Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

RDambrosio016
Copy link

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

@scottmcm scottmcm added the T-lang Relevant to the language team, which will review and decide on the RFC. label Nov 19, 2020
@scottmcm
Copy link
Member

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.)

@digama0
Copy link
Contributor

digama0 commented Nov 20, 2020

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.

@clarfonthey
Copy link

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.

@novacrazy
Copy link

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.

@RDambrosio016
Copy link
Author

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"

@RobbieMcKinstry
Copy link

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. :-)

@digama0
Copy link
Contributor

digama0 commented Nov 26, 2020

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.

@RDambrosio016
Copy link
Author

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

@RobbieMcKinstry
Copy link

RobbieMcKinstry commented Nov 28, 2020

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.

trait MyTrait { /* */ }
```

similar to `#[non_exhaustive]`, this was ultimately ruled out in favor of dedicated syntax.
Copy link
Member

@nagisa nagisa Dec 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this ruled out? #[non_exhaustive] is a valid attribute and the only way to make enums non-exhaustive currently. So to me it seems like non_exhaustive being an attribute would support #[object_safe] over dedicated syntax.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, perhaps ruled out was not the right wording. I was referring to a conversation i had on discord, in particular with scottmcm and we decided that it should be dedicated syntax similiar to dyn Trait. I am open for using an attribute instead however, although it would need to have accurate ranges in errors, i am not sure if that is possible so perhaps you could clear that up for me? thanks

@PoignardAzur
Copy link

PoignardAzur commented Jan 30, 2021

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".

@Kixiron
Copy link
Member

Kixiron commented Jan 30, 2021

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

@scottmcm
Copy link
Member

rust-lang/rust#87991 is a good example of the kind of accident that having a feature like this would help prevent.

@scottmcm scottmcm added I-lang-nominated Indicates that an issue has been nominated for prioritizing at the next lang team meeting. and removed I-lang-nominated Indicates that an issue has been nominated for prioritizing at the next lang team meeting. labels Sep 18, 2022
@Jules-Bertholet
Copy link
Contributor

Jules-Bertholet commented Oct 9, 2022

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.

@workingjubilee workingjubilee added the S-waiting-on-author Status: This is awaiting some action from the author. label Jan 2, 2025
@workingjubilee
Copy link
Member

We have in fact renamed "object safety", now: rust-lang/lang-team#286

@RDambrosio016 Do you intend to revise this to be current and address the alternatives proposed?

@workingjubilee workingjubilee added the A-trait-object Proposals relating to trait objects. label Jan 2, 2025
@SOF3
Copy link

SOF3 commented Jan 2, 2025

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. :-)

While this totally makes sense, I would like to add that static assertions are not tribal when the macro is explicitly named. var _ MyInterface = &Type{} could be confusing, but static_assert!(impl MyInterface for Type) is not confusing. I am not against this proposal, just saying it isn't as bad when the macro form becomes conventional. (And some static_assertions macros like negative impl assertions have very dubious error messages as well)

In addition to static assertions, many macro crates also employ similar techniques for custom errors, such as typed_builder generating a message of missing_field_required etc. I would say such hacks are bound to exist, and they are not intrinsically bad, but certainly something we can improve on at the proper cost.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-trait-object Proposals relating to trait objects. S-waiting-on-author Status: This is awaiting some action from the author. T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.