Trait
Traits define functionality that can be shared with several types. Traits are important in generic code. They also allow operator overloading and can provide information about types to the compiler.
Traits are similar to interfaces and type classes in other languages. Since Rust has no class inheritance, traits are the only mechanism for polymorphism.
Example
Here is a trait that allows combining two values of the same type into a new value, also of the same type:
pub trait Combine {
// Required method
fn combine(self, other: Self) -> Self;
// Provided method
fn fold<I>(values: I) -> Option<Self>
where
I: IntoIterator<Item = Self>,
Self: Sized,
{
let mut iter = values.into_iter();
let first = iter.next()?;
Some(iter.fold(first, Self::combine))
}
}
The trait has a required method and a provided method. Required methods don't have a body, so they must be implemented. Provided methods have a default implementation. They can be implemented, which overrides the default one, but they don't need to.
To use the trait, it must first be implemented. Let's do that:
impl Combine for i32 {
fn combine(self, other: Self) -> Self {
self + other
}
}
impl<T, U> Combine for (T, U)
where
T: Combine,
U: Combine,
{
fn combine(self, other: Self) -> Self {
(self.0.combine(other.0), self.1.combine(other.1))
}
}
The trait is now implemented for i32
and 2-tuples. The latter implementation is generic: It implements Combine
for any tuple (T, U)
, where T
and U
are types that implement the Combine
trait.
Let's try it out:
let t1 = (4, (5, 3));
let t2 = (5, (2, 3));
let t3 = (4, (7, 9));
println!("{:?}", Combine::fold(vec![t1, t2, t3]));
// prints "Some((13, (14, 15)))"
Coherence
Rust requires that trait implementations are coherent. This means that a trait cannot be implemented more than once for any type. For example, Combine
can't be implemented for (String, String)
, because this would overlap with the generic implementation for (T, U)
.
The current plan is to dramatically relax this restriction with [_ |-}}.html RFC 1210: specialization].
Orphan rules
The orphan rules are rules to enforce coherence in projects where some types and traits are used in multiple dependencies. In a way, they simplify dependency management, because they guarantee that implementations in different crates never conflict.
This is a (slightly modified) quote from coherence|[_ |-}}.html RFC 2451: re-rebalancing coherence] that explains the orphan rules:
Generally speaking, Rust only permits implementing a trait for a type if either the trait or type were defined in the your crate. However, Rust allows a limited number of implementations that break this rule, if they follow certain rules.
A trait or type is considered local when it is defined in your crate, otherwise it is considered foreign.
When implementing a foreign trait for a foreign type, the trait must have one or more type parameters. A type local to your crate must appear before any use of any type parameters. This means that
impl<T> ForeignTrait<LocalType<T>, T> for ForeignType
is valid, butimpl<T> ForeignTrait<T, LocalType<T>> for ForeignType
is not.The reason that Rust considers order at all is to ensure that your implementation does not conflict with one from another crate. Without this rule, you could write
impl<T> ForeignTrait<T, LocalType> for ForeignType
, and another crate could writeimpl<T> ForeignTrait<TheirType, T> for ForeignType
, which would overlap. For that reason, we require that your local type come before the type parameter, since the only alternative would be disallowing these implementations at all.
Super traits
Traits can inherit from other traits. This is expressed with trait bounds, e.g. trait Foo: Bar
, which means that Bar
is a super trait of Foo
. Every type that implements Foo
must also implement Bar
. Trait inheritance should not be confused with class inheritance in object-oriented languages.
In trait bounds, super traits don't need to explicitly specified:
trait Bar {
fn bar(&self);
}
trait Foo: Bar {
fn foo(&self);
}
fn function<T: Foo>(value: &T) {
value.foo();
value.bar();
}
Blanket implementations
Traits can be implemented for several types at once, thanks to generics. For example, it's possible to impl<T> Add for MyStruct<T>
. However, it's also possible to implement a trait for all types that satisfy a condition, or even all types unconditionally. This is called a blanket implementation:
trait MyTrait {}
// Implement it for all types that implement Copy
impl<T: Copy> MyTrait for T {}
To ensure coherence, traits with a blanket implementation can only be implemented for other types that can't conflict with the blanket implementation:
trait MyTrait {}
// blanket implementation
impl<T: Sized> MyTrait for T {}
// this is ok, because [T] is !Sized
impl<T> MyTrait for [T] {}
// this is NOT ok
impl<T> MyTrait for Box<T> {}
Note that inherent impl
s can't be blanket implementations, i.e. impl<T> T {}
is forbidden.
Trait objects
See also: Chapter about trait objects from The Rust Programming Language
Trait objects are objects that implement a specific trait, but their concrete type is unknown. Trait objects enable dynamic dispatch.
Example
fn print_all(objects: &[&dyn Display]) {
for object in objects {
println!("{}", object);
}
}
print_all(&[
&5_i32,
&"Hello, world!",
&true,
]);
Trait object are written as dyn Trait
, where Trait
is the trait which the trait object implements. Because their concrete type is unknown, trait objects are dynamically-sized types (DSTs). Values behind a reference or pointer are automatically coerced to trait objects when necessary, which can be observed in the above example in the print_all()
call.
Because their concrete type is unknown, trait objects implementing the same trait are considered the same type. This enables dynamic dispatch; for example, it's the reason why it's possible to put trait objects of different concrete types in an array.
Trait objects don't carry type information, so they can only access their trait methods. Downcasting a trait object to the concrete type is unsafe. One way to do it safely is to use the Any
trait.
Trait objects with bounds
Trait objects can't have additional bounds, except for lifetimes and auto traits. For example:
// allowed, because `Send` is an auto trait:
let _: &(dyn ToString + Send + 'static) = &42;
// not allowed:
let _: &(dyn ToString + Any) = &42;
This restriction can be bypassed by creating a new trait with several super traits:
trait ToStringAndAny: ToString + Any {}
// blanket implementation:
impl<T: ToString + Any> ToStringAndAny for T {}
Representation of trait objects
Trait objects, like all DSTs, can only exist behind a reference or pointer type, and are fat: They're twice as wide as regular references, because they contain two pointers: One pointer to the data, and one pointer to the virtual method table (vtable).
The vtable is a list of all methods of the trait. It includes the size and alignment of the type and a destructor, which calls the Drop
trait if it is implemented.
Object safety
Not all traits can be used as trait objects. One example are traits with a method that returns Self
: Calling such a method on a trait object would return a value with unknown size, which is not allowed. To prevent this, only traits that are object safe can be coerced to a trait object.
Which traits aren't object safe
These rules are defined in [_ |-}}.html RFC 0255: object_safety]. A trait is not object safe, if any of the following conditions are met:
The trait has a Sized
bound
trait Foo: Sized {
fn method(&self);
}
The trait Foo
inherits from Sized
, requiring the Self
type to be sized, but trait objects are not sized and don’t implement Sized
. Traits default to Self
being possibly-unsized – effectively a bound Self: ?Sized
– to make more traits object safe by default.
It references Self
There are two fundamental ways in which this can happen, as an argument or as a return value, in either case a reference to the Self
type means that it must match the type of the self
value, the concrete type of which is unknown at compile time. For example:
trait Foo {
fn method(&self, other: &Self);
}
The types of the two arguments have to match, but this can’t be guaranteed with a trait object, since its concrete type is unknown.
It has static methods
It's not possible to call static methods (functions without a self
parameter) of a trait object, because then the vtable can't be accessed, so this trait is not object safe:
trait Foo {
fn method() -> i32;
}
It has generic methods
Generic functions are monomorphised, that is, a copy of the function is created for each combination of types used as generic parameters. If generic methods in trait objects were allowed, their vtable could become very bloated because of the monomorphised functions. Therefore traits with generic methods are not object safe.
Workarounds
If a trait method violates object safety, it's still possible to make the trait object safe by adding a Self: Sized
bound to this method, but then the method is no longer accessible on trait objects:
trait Foo {
// this can't be used on trait objects:
fn method(&self, other: &Self)
where
Self: Sized;
}
Note that this might be undesired, as this trait can no longer be implemented for DSTs such as str
or [i32]
. An alternative is to split the trait in an object-safe trait an an object-unsafe sub-trait:
trait Foo {
// only has methods that don't violate object safety
}
trait FooExt: Foo {
fn method(&self, other: &Self);
}
Now Foo
is object safe; FooExt
can be used when we want to use method()
, and object safety is not required.
Unsafe traits
Unsafe traits are traits that are unsafe to implement; implementing them for certain types can cause undefined behavior. Examples include Send
and Sync
.
Example
/// Sets all bits to 0.
///
/// # Safety
///
/// Implementors of this trait are responsible that
/// these preconditions are satisfied:
///
/// * 0 is a valid value of the type.
/// * zeroing a value doesn't break assumptions
/// which the type relies on for safety.
unsafe trait MakeZero {
fn make_zero(&mut self) { todo!() }
}
// Safety: `false` is represented as 0
unsafe impl MakeZero for bool {}
// Safety: `None::<Box<T>>` is represented as 0
unsafe impl<T> MakeZero for Option<Box<T>> {}
Auto traits
See also: Reference – Special types and traits
Auto traits are traits that are automatically implemented for many types and have special properties. From the standard library, the Send
, Sync
, UnwindSafe
, and RefUnwindSafe
traits are auto traits.
If no explicit implementation or negative implementation is written out for an auto trait for a given type, then the compiler implements it automatically according to the following rules:
&T
,&mut T
,*const T
,*mut T
,[T; n]
, and[T]
implement the trait ifT
does.- Function item types and function pointers automatically implement the trait.
- Structs, enums, unions, and tuples implement the trait if all of their fields do.
- Closures implement the trait if the types of all of their captures do. A closure that captures a
T
by shared reference and aU
by value implements any auto traits that both&T
andU
do.
For generic types (counting the built-in types above as generic over T
), if a generic implementation is available, then the compiler does not automatically implement it for types that could use the implementation except that they do not meet the requisite trait bounds. For instance, the standard library implements Send
for all &T
where T
is Sync
; this means that the compiler will not implement Send
for &T
if T
is Send
but not Sync
.
Auto traits can also have negative implementations, written as impl !AutoTrait for T
, that override the automatic implementations. For example *mut T
has a negative implementation of Send
, and so *mut T
is not Send
, even if T
is.
Auto traits may be added as an additional bound to any trait object, even though normally only one trait is allowed. For instance, Box<dyn Debug + Send + UnwindSafe>
is a valid type.
Auto traits are marker traits, i.e. they cannot have methods or associated items.
Auto traits and negative implementations outside the standard library can only be used with experimental, nightly-only features:
#![feature(optin_builtin_traits, negative_impls)]
auto trait Foo {}
impl<T> !Foo for &mut T {}
Auto traits were originally called opt-in built-in traits (OIBITs), although they are neither opt-in nor built-in.
Sized trait
The Sized
marker trait is automatically implemented for all types that aren't dynamically sized, so their size is known at compile time. It is not an auto trait, but it behaves like one in some ways.
Types that implement Sized
- Most primitive types implement
Sized
:- Numeric types,
bool
andchar
- Function pointers (
fn
) - Arrays (
[T; n]
) - Pointers, references (
&T
,&mut T
,*const T
,*mut T
) - Never (
!
)
- Numeric types,
- Structs, Enums, Unions and Tuples implement
Sized
, if all of their fields do. - Closures implement
Sized
if the types of all of their captures do. - Special cases:
PhantomData<[u8]>
isSized
even though[u8]
is not. This is different from auto traits (PhantomData<T>
only implements auto traits that are implemented byT
).[()]
does not implementSized
, even though it is zero-sized.
A Sized
trait bound is implicitly added to all generics:
// this generic function...
fn func<T>(t: &T) {}
// ...desugars to...
fn func<T: Sized>(t: &T) {}
// to remove the implicit bound:
fn func<T: ?Sized>(t: &T) {}
To remove this default trait bound, a relaxed trait bound ?Sized
can be added; this means that the type may or may not be sized. Sized
is the only trait where a relaxed trait bound can be used.
Only Sized
types can be used on the stack and passed by value:
fn func<T: ?Sized>(t: T) {}
Neither Sized
nor !Sized
can be manually implemented for a type.
?Sized
can't be used as a super trait:
trait Foo: ?Sized {}
The reason is that traits are ?Sized
by default, so more traits can be used as trait objects. If a trait should be restricted to sized traits, a Sized
bound must be added:
trait Foo: Sized {}