lotsoftools

Understanding Rust Error E0038: Non-Object-Safe Traits

Introduction to Rust Error E0038

Rust Error E0038 occurs when attempting to use a trait object type for a trait that is not object-safe. Object-safe traits can be used as trait object types, typically written as 'dyn Trait'. In earlier Rust editions, trait object types were written as plain Trait, but this was confusing and has since been changed to 'dyn Trait'.

Two main aspects of trait object types lead to the restrictions associated with object-safe traits:

Dynamically Sized Types (DSTs)

Trait object types are dynamically sized types (DSTs). They can only be accessed through pointers, such as '&dyn Trait' or 'Box<dyn Trait>'. The size of the pointer is known, but the size of the 'dyn Trait' object pointed-to is opaque, and different trait objects with the same trait object type can have different sizes.

Virtual Method Tables (Vtables)

The pointer to a trait object is paired with an extra pointer to a 'virtual method table' or 'vtable'. This is used to implement dynamic dispatch to the object's implementations of the trait's methods. There is one vtable for each trait implementation, but different trait objects with the same trait object type may point to vtables from different implementations.

Object-Safety Violations

Specific conditions that violate object-safety are primarily related to missing size information and vtable polymorphism, which arise from the aspects outlined above.

The Trait Requires Self: Sized

Traits declared as 'Trait: Sized' or that inherit a constraint of 'Self: Sized' are not object-safe. The restriction helps simplify error reporting and interoperability between static and dynamic polymorphism.

Code Example: Static Method and Trait Object

trait Trait {
}

fn static_foo<T: Trait + ?Sized>(b: &T) {
}

fn dynamic_bar(a: &dyn Trait) {
    static_foo(a);
}

Method References the Self Type in its Parameters or Return Type

This error occurs when a trait has a method that references the Self type in its parameters or return type. However, '&self' and '&mut self' are acceptable. To resolve this error, add a 'where Self: Sized' bound on the methods that aren't object-safe.

Code Example: Self Type in Method

trait Trait {
    fn foo(&self) -> Self;
}

Method Has Generic Type Parameters

This error occurs because trait objects contain pointers to method tables. This usually isn't a problem, but when a method has generic parameters, it can lead to issues, since the compiler needs to create a table for each different type implementing the trait. The suggested fix is to use a 'where Self: Sized' bound or replace the type parameter with another trait object.

Method Has No Receiver

Methods without a self parameter can't be called, as there won't be a way to get a pointer to the method table for them. Adding a 'Self: Sized' bound to these methods will generally resolve this issue.

Trait Contains Associated Constants

As with static functions, associated constants aren't stored on the method table. If the trait or any subtrait contains an associated constant, they cannot be made into an object. A workaround is to use a helper method instead.

Trait Uses Self as a Type Parameter in the Supertrait Listing

This error is similar to the second violation, but subtler. It occurs when the supertrait might have an object-safe method but derives from 'Super<Self>', causing the method to return an object of unknown type. The fix typically involves refactoring the code to no longer need to derive from 'Super<Self>'.