Interfaces
Compile-time capability interfaces, bounds, fields, default methods, and marker interfaces.
Interfaces describe the fields and methods a type must provide. They are most often used as generic bounds, so a generic function can call required methods or access required fields.
Interfaces are static contracts in the current language. They are not runtime value types: there
is no dyn Interface, trait object, existential interface value, fat pointer,
vtable layout, dynamic-dispatch object model, or runtime interface ABI.
interface HasValue<T> {
value: T,
}
struct Box: HasValue<i32> {
value: i32,
}
fn read_value<T: HasValue<i32>>(item: T) -> i32 {
return item.value;
}
fn main() {
let box = Box { value: 42 };
assert read_value<Box>(box) == 42;
}
Interface methods use normal method syntax. A struct lists implemented
interfaces after :.
interface Writer {
fn write(self, value: i32);
}
struct Counter: Writer {
total: i32,
fn write(self, value: i32) {
self.total += value;
}
}
fn write_three<W: Writer>(writer: &W) {
writer.write(3);
}
fn main() {
let counter = Counter { total: 0 };
write_three(&counter);
assert counter.total == 3;
}
The implementation must match the interface member names, parameter types,
self reference shape, and return type.
An interface method with a body is a default method. Implementing structs can use it without declaring their own method.
interface CounterSink {
fn write(self, value: i32);
fn write_twice(self, value: i32) {
self.write(value);
self.write(value);
}
}
struct Counter: CounterSink {
total: i32,
fn write(self, value: i32) {
self.total += value;
}
}
fn main() {
let counter = Counter { total: 0 };
counter.write_twice(7);
assert counter.total == 14;
}
If a struct provides a method with the same name as a default method, its signature must still match the interface.
Interfaces can inherit other interfaces. A declaration can either provide a body or end with a semicolon when it only combines other interfaces.
interface Readable {
fn read(self) -> i32;
}
interface Writable {
fn write(self, value: i32);
}
interface Stream: Readable, Writable;
A struct that implements Stream must satisfy both Readable and Writable.
Interfaces can require operator methods. Generic code can then use the operator through an interface bound.
interface Addable<T> {
+(self, other: const &T) -> T;
}
struct Number: Addable<Number> {
value: i32,
+(self, other: const &Number) -> Number {
return Number { value: self.value + other.value };
}
}
fn add<T: Addable<T>>(a: T, b: T) -> T {
return a + b;
}
fn main() {
let a = Number { value: 10 };
let b = Number { value: 32 };
let sum = add<Number>(a, b);
assert sum.value == 42;
}
See Operators for the full overloadable operator list.
An interface name may appear as a generic constraint, a struct conformance
entry, a base interface, or the compiler-created self contract inside an
interface method. It is invalid as a concrete value/storage type, including for
locals, fields, parameters, returns, arrays, slices, nullable forms,
references, raw pointers, aliases, and generic arguments.
Use a generic parameter constrained by the interface instead:
fn read_from<R: Reader>(reader: &R) -> usize {
return reader.read();
}
std.builtin declares marker interfaces that the compiler treats specially.
| Interface | Implemented by |
|---|---|
Copy | copyable built-ins and structs that explicitly implement Copy |
Integer | primitive signed and unsigned integer types except bool |
Signed | i8, i16, i32, i64, i128, isize |
Unsigned | u8, u16, u32, u64, u128, usize |
Signed and Unsigned imply Integer. Structs can explicitly implement
Copy when all fields are copyable, but structs cannot implement Integer,
Signed, or Unsigned.
See Move, Copy, and Clone for copy eligibility, move-only values, clone APIs, and drop behavior.
- Interface fields require matching field names and exact field types.
- Interface methods require matching method names, parameters,
selfshape, and return type. - Default methods count as implemented unless a struct declares a mismatched method with the same name.
- Generic interfaces can be used as bounds, for example
T: HasValue<i32>. - Interface bounds are checked during overload resolution and specialization.
- Default methods are statically resolved; they are not virtual calls.
See Generics for bounds and numeric marker constraints.