rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

Add a new syntax to declare that a trait must always be object-safe

Open RDambrosio016 opened this issue 3 years ago • 12 comments

This RFC proposes a minor addition to the trait declaration syntax as follows:

dyn trait MyTrait { /* */ }

This syntax would enforce that the trait be object safe under all circumstances. This is done as a way to natively support a common design pattern for trait objects and make errors friendlier.

Pre-RFC discussion

Rendered

RDambrosio016 avatar Nov 19 '20 02:11 RDambrosio016

Thanks for the RFC! I'm definitely in favour of making common patterns be "real" features where feasible -- rather than having people make unused functions and such.

(Similar to how I like #[non_exhaustive] even though a private () mostly worked.)

scottmcm avatar Nov 19 '20 19:11 scottmcm

I will put my vote in for #[object_safe] trait over dyn trait, at least with the current semantics. For me dyn trait reads too much like a trait that can only be in a trait object (although that doesn't really make sense as a feature), and also the point brought up in the pre-RFC that this would tell users immediately if a trait is object safe or not, is false with the current design because the keyword is optional. I would prefer to reserve the keyword version for "mandatory for object safe traits" if and when we ever go that route. As it is currently, this is just one more thing for the static_assertions crate.

I will also put in a vote for #[non_object_safe] trait to disable dyn Trait for semver stability.

digama0 avatar Nov 20 '20 18:11 digama0

After giving less than adequate thought: what about implicitly adding a where Self: Sized bound to methods as needed to ensure object safety? I see this a lot and am not sure why this would be a better approach.

Personally feel like dedicated syntax would be nice but not sure if dyn trait is the best way to do it. Attribute makes more sense IMHO.

clarfonthey avatar Nov 21 '20 05:11 clarfonthey

IMO this is very easy to statically assert with just:

const _: Option<&dyn MyTrait> = None;

though perhaps a procedural attribute macro could make it even easier.

However, it's worth noting that associated type bounds can be part of the trait object, such as modifying the above to be Option<&dyn MyTrait<AssocType = i32>>, so it may be difficult to assert that a trait is object safe without knowing exactly what the associated types can be.

novacrazy avatar Nov 26 '20 04:11 novacrazy

IMO this is very easy to statically assert with just:

const _: Option<&dyn MyTrait> = None;

though perhaps a procedural attribute macro could make it even easier.

However, it's worth noting that associated type bounds can be part of the trait object, such as modifying the above to be Option<&dyn MyTrait<AssocType = i32>>, so it may be difficult to assert that a trait is object safe without knowing exactly what the associated types can be.

Yes, statically asserting that a trait is object safe is simple, however, it does not solve the issue of inaccurate error ranges. Moreover, the point of this rfc is to natively support such a pattern without "hacks"

RDambrosio016 avatar Nov 26 '20 04:11 RDambrosio016

IMO we don't want Rust to be a language where you have to learn a set of tribal knowledge patterns to be effective. Go has a similar pattern to assert interface satisfaction:

var _ MyInterface = &MyConcreteType{}

It's unfortunate that if you haven't seen this pattern before, it would be quite challenging to arrive at it yourself. That's how I feel about const _: Option<&dyn MyTrait> = None;. I'm certainly going to use it in my next project! But I wish I had a procedural macro to codify this technique. :-)

RobbieMcKinstry avatar Nov 26 '20 05:11 RobbieMcKinstry

But I wish I had a procedural macro to codify this technique. :-)

Have you seen the static_assertions crate? That's usually where this "tribal knowledge" gets codified.

digama0 avatar Nov 26 '20 07:11 digama0

But I wish I had a procedural macro to codify this technique. :-)

Have you seen the static_assertions crate? That's usually where this "tribal knowledge" gets codified.

Yes, but most people wont add a dependency for such a small thing, i dont think i have ever seen the crate be used for that over just making a private function with a dyn parameter

RDambrosio016 avatar Nov 26 '20 15:11 RDambrosio016

But I wish I had a procedural macro to codify this technique. :-)

Have you seen the static_assertions crate? That's usually where this "tribal knowledge" gets codified.

I have. Thank you for sharing, though! A wonderful crate! :-) For new programmers, knowledge of this crate is tribal. It's not an official crate blessed by the Rust team, not part of the Rust book, etc. It's a widely used, well-known crate, but one that new Rustaceans might not be aware of.

Further, this crate doesn't provide a static guarantee that the trait is object-safe. You have to execute the assertion statement -- a dynamic behavior -- to determine object-safety. The crate lets you make assertions about the static properties of types, but they're made at runtime.

RobbieMcKinstry avatar Nov 28 '20 04:11 RobbieMcKinstry

Quick bikeshed: a lot of people have expressed that the name "object-safe" is a little confusing. If we do go the attribute approach, it would be nice to brainstorm an official name for the concept.

Personally, I'm in favor of "dyn-safe".

PoignardAzur avatar Jan 30 '21 13:01 PoignardAzur

Object safety is already used pervasively throughout the documentation, this RFC isn't the place to try and change that

Kixiron avatar Jan 30 '21 14:01 Kixiron

https://github.com/rust-lang/rust/issues/87991 is a good example of the kind of accident that having a feature like this would help prevent.

scottmcm avatar Aug 18 '21 19:08 scottmcm

I think, if Rust adds a new syntax to indicate explicit dyn-safety, that should also be an opportunity to re-evalutate the object safety rules in a backward-compatible way.

There are multiple other proposed language features that would benefit from something similar to object safety:

  • Automatically implementing certain traits for the never type, or allowing overlapping implementations of those traits for the never type.
  • Some sort of "enum impl Trait" mechanism for static dispatch (see discussions on IRLO). This would allow you to write a function that has multiple return statements, returning different types but which all implement a common trait, and the compiler would generate an enum with one variant per type, implement the trait for the type via delegation, and wrap the function return values in said enum.

Both of these proposals (and probably many more, past, present, and future) rely on the concept of "a trait that is only useful when you have a value of a type that implements it." Current object safety is almost this, but there's a catch: anything marked Self: Sized doesn't have to obey the rules. This makes current object safety useless for all these proposals.

Therefore, I propose that explicitly annotated dyn trait declarations should add additional restrictions on the trait, in addition to the current object safety rules:

  • Such traits should not be allowed to have any associated constants or types (return-position-impl-trait possibly excepted)
  • All associated methods of such a trait must take Self as an argument in some way.

These rules would, I suspect, cover the vast majority of existing object-safe traits, while also making object safety a more general property that applies neatly to many other language features as well

What does it mean to "take Self as an argument in some way"? I'm not sure of the best way to specify this, but the rules would probably involve some combination of DispatchFromDyn and the types of the identifiers you could potentially bind by destructuring the argument in an irrefutable pattern match.

Jules-Bertholet avatar Oct 09 '22 04:10 Jules-Bertholet