Rust Object Safety Rules
Object safety is the set of rules a trait must pass to become a trait object. Some traits fail them and cannot become objects.
Safe: plain methods on &self
A method that needs only the receiver can sit in the vtable — the table holds one fixed address for it.
trait Tool {
fn name(&self) -> String;
fn use_it(&mut self);
}Breaker 1: a method returns Self
Through a trait object the real type is erased, so nobody knows what Self is — or how big it is. Refused with error E0038 (checked).
trait Copyable {
fn duplicate(&self) -> Self; // breaks object safety
}Breaker 2: a generic method
A vtable holds one address per method. A generic method would need one address per type T — an endless table. Also E0038.
trait Converter {
fn convert<T>(&self, value: T) -> String; // breaks object safety
}Breaker 3: Sized as a supertrait
A trait object is unsized by nature — its concrete type is hidden. A trait that demands Self: Sized fights that directly, and is refused as an object.
trait Frozen: Sized {
fn label(&self) -> String;
}Breaker 4: an associated constant
Constants live on the type, not on the value — and a trait object has forgotten its type. A trait with an associated const cannot be made into an object either.
trait Named {
const NAME: &'static str;
fn greet(&self) -> String;
}Fix one: excuse a method with where Self: Sized
The method stays on the trait for concrete types, but leaves the vtable. Trait objects simply cannot call it — everything else still works.
fn fresh_copy(&self) -> Self
where
Self: Sized;Fix two: return a box, not Self
A Box<dyn Tool> has a known size even when the type inside is erased — so this shape is dispatchable.
fn boxed_copy(&self) -> Box<dyn Tool>;Example
trait Tool {
fn name(&self) -> String;
// Not dispatchable — but allowed, because of the where clause.
// Concrete types can call it; trait objects skip it.
fn fresh_copy(&self) -> Self
where
Self: Sized;
// The object-safe way to “copy”: hand back a box instead of Self.
fn boxed_copy(&self) -> Box<dyn Tool>;
}
#[derive(Clone)]
struct Hammer {
weight: u32,
}
impl Tool for Hammer {
fn name(&self) -> String {
format!("hammer {} kg", self.weight)
}
fn fresh_copy(&self) -> Self {
self.clone()
}
fn boxed_copy(&self) -> Box<dyn Tool> {
Box::new(self.clone())
}
}
fn main() {
let tools: Vec<Box<dyn Tool>> = vec![Box::new(Hammer { weight: 2 })];
// Dispatchable methods work through the trait object.
println!("{}", tools[0].name());
// boxed_copy returns a trait object, so it works here too.
let again = tools[0].boxed_copy();
println!("{}", again.name());
// fresh_copy needs a known type — call it on the concrete value.
let real = Hammer { weight: 5 };
let copy = real.fresh_copy();
println!("{}", copy.name());
}Key points
- A trait is object safe when every method can sit in a vtable: one fixed address, called through a pointer alone.
- Object safety is now called dyn compatibility.
- The reference and error E0038 (“the trait is not dyn compatible”) use the new name.
- The rules are the same.
- The vtable test: a method is dispatchable only under one condition.
- The compiler must store one fixed address for it.
- It must call it knowing nothing but the pointer.
- Receivers that work: &self, &mut self, self: Box<Self>, Rc<Self>, Arc<Self>, and Pin versions of them.
- A bare self taken by value is not dispatchable.
- Every supertrait must be object safe too: one unsafe supertrait poisons the whole chain
- Associated types are allowed in trait objects.
- But they must be pinned in the type.
- Box<dyn Iterator<Item = u8>> names Item once and for all.
- These rules bite only when you write the word dyn.
- Two standard fixes can rescue a trait that breaks a rule.
In this example, Tool carries both fixes at once. fresh_copy returns Self, which no vtable can hold. The where Self: Sized clause excuses it from the vtable. So Box<dyn Tool> still compiles. The concrete Hammer can still call it (the 5 kg line). boxed_copy avoids the problem entirely. It returns Box<dyn Tool>, a type with a known size. So the trait object can call it. The call produces the second 2 kg line. Without either fix, the very first Box<dyn Tool> line would fail with E0038.
Note
Generic methods and Self returns are perfectly fine in generic code (T: Copyable).
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
Which method keeps a trait from being used as a trait object?