Help understanding object-safe methods vs. traits

Hi all, I’m a Go programmer trying to understand Rust design choices.

It seems from RFC 255 that Rust used to allow every trait to be a type, and would reject calls x.m(y) where x was a trait object and m was a method on the trait that was not object-safe.

Instead, following the RFC, Rust currently will not allow a trait to act as a type unless all of its methods are object-safe.

The RFC motivates the change with this paragraph:

Software evolution is improved with this proposal: imagine adding a non-object- safe method to a previously object-safe trait. With this proposal, you would then get errors wherever a trait-object is created. The error would explain why the trait object could not be created and point out exactly which method was to blame and why. Without this proposal, the only errors you would get would be where a trait object is used with a generic call and would be something like “type error: SomeTrait does not implement SomeTrait” - no indication that the non-object-safe method were to blame, only a failure in trait matching.

There are two things I don’t understand about this paragraph. One is the error it claims you get “where a trait object is used with a generic call.” I’m unclear exactly what that means, and not sure what trait matching is, but I’d think in a case like x.m(y) you’d be able to generate an error that says “you can’t call m because it’s not object-safe,” which seems pretty clear.

The other thing I don’t get is why checking object-safety at the trait level (current design) is better for software evolution than checking it at the method level (old design). With trait-level checking, if you add a method that is not object-safe to a previously object-safe trait, you will immediately break everyone who uses the trait as a type. But if you only check for object-safe methods, you will not break anyone now (because no one could possibly be calling the method), and anyone who tries to call the method in the future will get an error. Trait-level checking can break working code, but method-level checking cannot.

Apologies for my ignorance and thanks in advance for your explanations.

1 Like

Unless someone was relying on Trait: Trait. Once you add a non-object-safe method to a trait you can no longer pass a trait object to a generic function that takes that trait.

eg.

fn foo<T: MyTrait>(t: T) { ... }
...
let t: MyTrait = something;
foo(t)

Even with method-level checking, this code can't compile once you add non-object safe method because the type signature of foo says that it might want to call that method. Even if we know it doesn't (since that function was written before we added the method), we still want function signatures to be able to say exactly what types a function can and cannot be called with.

1 Like