-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
RFC: Implementable trait aliases #3437
base: master
Are you sure you want to change the base?
RFC: Implementable trait aliases #3437
Conversation
- Better ergonomics compared to purely proc-macro based solutions. | ||
- One alternative is to allow marker traits or auto traits to appear in `+` bounds of implementable aliases. | ||
(For example, `trait Foo = Bar + Send;` could be made implementable). However, I suspect that the complexity would not be worthwhile. | ||
- Another possibility is to require an attribute on implmenentable aliase; e.g. `#[implementable] trait Foo = ...`. Again, I don't think that the complexity is warranted. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one benefit of requiring #[implementable]
is that it mitigates the confusing aspect of:
#[implementable] // error: must only have one trait on rhs of equal -- generated because of #[implementable]
pub trait Foo = Bar + Baz;
// vs.
#[implementable]
pub trait Foo = Bar
where
Self: Baz;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added some more discussion about this.
As an aside, it'd be nice if type aliases permitted instantiation:
|
they do, if you use curly braces (except for tuples): i think it'd also be nice to allow curly brace syntax for tuples for ease of writing proc macros so there's one consistent syntax that works on all struct-like types: type T<A, B> = (A, B);
let v = T::<_, _> { 0: "abc", 1: 123 };
match v {
T::<_, _> { 0: a, 1: b } => println!("it works! a={a} b={b}"),
} |
Wait, so |
Changing this behavior would be a breaking change, for code like the following: struct Foo();
type Bar = Foo;
fn Bar() {} Anyway, this is all a bit off-topic for the RFC 😁 |
I think that the RFC should mention #1672 and mutually exclusive traits rust-lang/rust#20400 Is this RFC an alternative to #1672 or is it orthogonal? |
@nielsle I don't see any relationship between this RFC and that issue. |
I think the summary motivation section should be fleshed out more, ideally with real-world examples (possibly simplified). After looking through the text this does seem useful but it wasn't obvious at all to me what the feature was from those sections. |
I've specified that implementable trait aliases also support fully-qualified method call syntax. |
@rustbot +I-lang-nominates +I-types-nominated I'm nominating this for @rust-lang/lang and @rust-lang/types discussion. I myself am in favor of this RFC. I've seen a lot of demand for a simplified version of trait aliases where-- (A) We only support I expect this to be relevant to async fn in traits as well (cc @tmandry) it's quite common to have something like Restricting to point (A) has the advantage of avoiding some complex corner cases. Note though that I do want to support the "inline bound" syntax, so that you could do |
I guess that my question to the lang/types teams, respectively, are:
|
One though -- @Jules-Bertholet -- I'm not sure if the RFC covers it, I didn't have time to read in super detail, but I think that if you are implementing I have, for a long time, wanted the ability to implement a trait and its supertraits together in a single impl block, I wonder if it's worth thinking about that as well, though I'd probably want to separate it out from this RFC. |
The RFC does not do any sort of trait unioning like this, I hadn't even considered it as a possibility (I will add it to the alternatives section). Notably, you would need to handle name collisions. |
I've added text to the alternatives section addressing @nikomatsakis's "unioning" idea. |
Yes. Presumably that would be an error. |
That would have backward-compatibility implications, if one of the traits adds a defaulted method that conflicts. Technically "minor" breakage, but still not ideal. |
@rustbot labels -I-lang-nominated This was discussed in the T-lang design meeting today. People do seem to want this in some form, but the RFC is likely going to need some iteration. Let's remove the nomination while we give time for this iteration to be done. Please renominate when you think T-lang should look back into this. |
I've made some changes in response to the design meeting (Zulip thread, HackMD) held earlier today. Notably, I've:
One thing I haven't yet updated is the motivation section. Many people have pointed out that they would like motivating examples that don't rely on unstable features. I plan to get to that Soon™—but in the meantime, if anyone has a good real-life example, feel free to share. Also in the design meeting, many people had thoughts on this RFCs relationship to |
I found the motivation in the RFC a bit off-target. I think that the 'weak-strong' concept is correct, but not really specific to trait aliases. Every subtrait corresponds to a "refined" view of the supertrait (sometimes with extra capabilities/methods as a result of that refinement). I think of trait aliases as really just a shorthand for declaring refined traits in cases where no "per-type behavior" is needed (i.e., because there are no new methods, or even (eventually) because the definition of the methods is always the same). To my mind, the real motivation here is expanding on the idea of trait aliases as being able to provide a "complete abstraction", where users don't have to know about the "contents" of the alias, they can treat it "as if" it were a standalone trait. I think the RFC started in the right vein there -- trait aliases are used to provide a "simplified, convenient view" onto a more complex underlying reality, but they're not able to truly hide that from users. Eventually I would like it if users could do the same for any trait -- i.e., if I have a trait |
I agree with this framing, that's a good way of putting it. |
I've added a subsection to the future possibilities that explores allowing trait aliases to define their own associated types and consts, and the additional backward-compatibility benefits that would bring. |
Having given it some more thought, I now suspect that associated types in trait aliases could be sufficient to GATify pub trait LendingIterator {
type LentItem<'a>
where
Self: 'a;
fn next<'a>(&'a mut self) -> Option<Self::LentItem<'a>>;
}
pub trait Iterator = LendingIterator
where
// Still need to resolve implied `'static` bound problem
// (https://blog.rust-lang.org/2022/10/28/gats-stabilization.html#implied-static-requirement-from-higher-ranked-trait-bounds)
for<'a> Self::LentItem<'a> = Self::Item,
{
type Item;
} |
I've added a section to the future possibilities discussing this. |
I've rewritten the AFIT example in the motivation section in terms of the |
There's one issue I hadn't noticed before that limits the applicability of this feature to a degree. Currently, if you have: pub trait Foo { /* ... */ }
pub trait SendFoo: Foo + Send {}
impl<T: Foo + Send + ?Sized> SendFoo for Foo {} Then, pub trait Foo { /* ... */ }
pub trait SendFoo = Foo where Self: Send; Makes these into the same type, which is a breaking change (trait impls could start overlapping). This isn't a problem for the async |
@rustbot labels +I-lang-nominated We discussed this in the lang planning meeting today, and it looks like there have been updates since we last looked at this, so let's renominate so we can discuss. |
@Jules-Bertholet I have another use case that I haven't seen mentioned here: I want to be able to hide a trait behind a module using a proc macro. I want to do this to create a sort of RTTI system for tracking which types implement traits dynamically (along with constructing other forms of metadata about the implementation). This is all in service of building a scripting language on top of Rust. Here's a really simplified example: #[runtime_trait]
pub trait Foo {
fn bar(&self) -> i32;
} would be rewritten to #[doc(hidden)]
trait __Foo {
fn bar(&self) -> i32;
}
pub mod Foo {
pub trait Foo = super::__Foo;
#[::linkme::distributed_slice]
pub static IMPLEMENTORS: [TypeId];
} And the impl: #[runtime_trait_impl]
impl Foo for T {
fn bar(&self) -> i32 { 3 }
} would be rewritten to: // Relies on being able to impl __Foo through the trait alias inside the module.
impl Foo::Foo for T {
fn bar(&self) -> i32 { 3 }
}
#[::linkme::distributed_slice(Foo::IMPLEMENTORS)]
static __FOO_T: TypeId = TypeId::of<T>();
TL;DR I think this would be really useful for syntactic manipulations with proc macros and would give macro authors more flexibility in rewriting traits. |
I have a real-world example as well. My
Then each protocol can define |
@Kixunil: Thanks for the report of the real-world example. That pattern is called "tagged impls". It provides one way to work around the current orphan rules. E.g.: Implementable trait aliases do indeed make this pattern more convenient. |
- One alternative is to allow marker traits or auto traits to appear in `+` | ||
bounds of implementable aliases. (For example, `trait Foo = Bar + Send;` could | ||
be made implementable). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm in favor of this, because while the symmetry with impl blocks is nice I think the symmetry with bounds is ultimately more important (and more confusing to break). If other lang team members agree I think we should go ahead and support it on day 1.
Otherwise, given that this is something we can allow in the future, can it be listed as a future possibility?
We discussed this RFC in our triage meeting today. The meeting consensus was that we'd ideally like to have an RFC that lays out a forward looking plan that goes a bit further than the specifics in this RFC:
Together (I think) these changes also allow for other transformations. It'd be good to capture these as part of the RFC's motivation. In addition to the "trait alias" = "trait + impl" equivalence above, the other equivalence we had in mind was being able to convert Note that just because the RFC includes this content doesn't mean we have to stabilize or even implement all of it at once. I think it'd be useful to identify "tiers" of support. In particular Trait aliases that include bounds with one primary trait + auto or marker traits but no where-clauses seems like an important tier that more-or-less works today. |
This wasn't discussed in the meeting, but I personally would go further and say that my ideal is that you can do |
Rendered
Allow writing
impl
blocks for certain trait aliases.Prior discussion on Internals
@rustbot label A-traits