Trait

From Rust Community Wiki
(Redirected from Trait objects)

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 i32This links to official Rust documentation 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, but impl<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 write impl<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> {}

error[E0119]: conflicting implementations of trait `MyTrait` for type `std::boxed::Box<_>`: | 4 | impl<T: Sized> MyTrait for T {} | ---------------------------- first implementation here ... 10 | impl<T> MyTrait for Box<T> {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `std::boxed::Box<_>`


Note that inherent impls 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 AnyThis links to official Rust documentation 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;

error[E0225]: only auto traits can be used as additional traits in a trait object | 5 | let _: &(dyn ToString + Any) = &42; | -------- ^^^ | | | | | additional non-auto trait | | trait alias used in trait object type (additional use) | first non-auto trait | trait alias used in trait object type (first use)

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 DropThis links to official Rust documentation 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 SendThis links to official Rust documentation and SyncThis links to official Rust documentation.

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 SendThis links to official Rust documentation, SyncThis links to official Rust documentation, UnwindSafeThis links to official Rust documentation, and RefUnwindSafeThis links to official Rust documentation 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 if T 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 a U by value implements any auto traits that both &T and U 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 SizedThis links to official Rust documentation 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 and char
    • Function pointers (fn)
    • Arrays ([T; n])
    • Pointers, references (&T, &mut T, *const T, *mut T)
    • Never (!)
  • 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]> is Sized even though [u8] is not. This is different from auto traits (PhantomData<T> only implements auto traits that are implemented by T).
    • [()] does not implement Sized, 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) {}

error[E0277]: the size for values of type `T` cannot be known at compilation time | 1 | fn func<T: ?Sized>(t: T) {} | - ^ doesn't have a size known at compile-time | | | this type parameter needs to be `std::marker::Sized` | = help: the trait `std::marker::Sized` is not implemented for `T` = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait> = note: all local variables must have a statically known size = help: unsized locals are gated as an unstable feature

Neither Sized nor !Sized can be manually implemented for a type.

?Sized can't be used as a super trait:

trait Foo: ?Sized {}

error: `?Trait` is not permitted in supertraits

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 {}