-
Notifications
You must be signed in to change notification settings - Fork 1.6k
RFC: enable derive(From)
for single-field structs
#3809
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
base: master
Are you sure you want to change the base?
Conversation
Fully in support of this. A few things that probably should be elaborated:
The transparent case, IMHO, is probably best left as a future extension, but it's an interesting case where |
Good point with the generics, I'll add a mention to the RFC. |
"For later" - perhaps it could recognize #3681 fields and automatically skip them as well. #[derive(From)]
struct Foo {
a: usize, // no #[from] needed, because all other fields are explicitly default'ed
b: ZstTag = ZstTag,
c: &'static str = "localhost",
}
// generates
impl From<usize> for Foo {
fn from(a: usize) -> Self {
Self { a, .. }
}
} OTOH we may still want the #[derive(From, Default)]
struct Foo2 {
#[from] // <-- perhaps still want this
a: u128 = 1,
b: u16 = 8080,
} |
The crate named derive-new creates a But perhaps this functionality belongs in a third party crate rather than the standard library. |
|
||
We could make `#[derive(From)]` generate both directions, but that would make it impossible to only ask for the "basic" `From` direction without some additional syntax. | ||
|
||
A better alternative might be to support generating the other direction in the future through something like `#[derive(Into)]`. |
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.
It might be worth noting here that impl From<Newtype> for Inner
and impl Into<Inner> for Newtype
have slightly different semantics. Using derive(Into)
to mean impl From
could be confusing for generic types where there is a coherence issue, even if it does imply an Into
impl.
(I've had similar issues with the derive_more version.)
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.
the derive-macro's name does not necessarily correspond to the impl'ed trait name anymore since #3621 (currently named #[derive(CoercePointee)]
, which actually does impl CoerceUnsized + DispatchFromDyn
.)
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.
The #[derive(Into)]
example is kind of hand-waving, I'm not sure if it's actually a good idea. I would like to avoid doing that in this RFC though, as that sounds like a separate can of worms, I explicitly tried to keep this RFC as simple as possible.
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.
the derive-macro's name does not necessarily correspond to the impl'ed trait name anymore since #3621 (currently named
#[derive(CoercePointee)]
, which actually doesimpl CoerceUnsized + DispatchFromDyn
.)
Understood! But my comment was more about the confusing semantics, than the exact name of the impl
'd trait.
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.
Honestly, for the impl From<NewType> for Inner
case, I think I would prefer something like impl_from!(Newtype -> Inner)
or a more generic impl_from!(Newtype -> Inner = |source| source.0)
or impl_from!(Newtype -> Inner = self.0)
(restricted to using fields of NewType)
Although it is a little annoying, to have to repeat the type of Inner.
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.
But my comment was more about the confusing semantics
I don't think there is any name more appropriate than #[derive(Into)]
especially if this RFC is using #[derive(From)]
. The final effect you get is still having an impl Into<Inner> for Self
effectively, through the intermediate impl From<Self> for Inner
+ the blanket impl.
See JelteF/derive_more#13 for a brief discussion how derive_more still chooses to name it #[derive(Into)]
.
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'd love to have a derive in the other direction, but full support for making that future work and not part of this RFC.
While |
Co-authored-by: teor <teor@riseup.net>
I think this proposal makes sense as written, and it's simple and straightforward. Should we allow @rfcbot merge |
Team member @joshtriplett has proposed to merge this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. |
I very much encourage distinct syntax for distinct directions. In particular, as noted in the On the other hand, going back to the inner type should never violate any invariant, and therefore |
Unsure: is this really lang, @joshtriplett? It's an impl that could be done in a stable proc macro crate, AFAICT, which to me says that it's entirely libs-api (even if in actual implementation it'd be done inside the compiler today). Personally I'm not concerned with "well this |
As a comparable, we handled RFC #3107 ( |
@traviscross proposal cancelled. |
@rfcbot fcp merge (And I'll recheck the box for Josh.) |
Team member @traviscross has proposed to merge this. The next step is review by the rest of the tagged team members: Concerns:
Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. |
(quoting @BurntSushi above)
TBH, while I don't disagree with what I wrote earlier, the arguments about which direction of from is more useful does seem relevant to me. If I have #[derive(From)]
pub struct Even(u32); Then the Thus it does feel odd to me that we'd add this RFC's less-applicable case without having something for the more-applicable case (aka the
I would be perfectly happy for libs-api to take over the FCP and thus I not have to decide one way or the other 🙃 |
I think that depends a lot on your use-cases. In terms of the code I write, I use newtypes without additional invariants for something like 90% of use-cases. PullRequestId, UserId, TaskId, WorkerId, I almost always just need literally a new type, without any invariants. That's the case where this derive helps the most. I agree that the other direction is also very useful, but I don't think we need to resolve that in this RFC, unless you can imagine a situation where |
I guess looking at #3809 (comment) all of libs-api has checked their boxes, so I'll just abstain to allow their decision to land, and if they end up concerning anything because of discussion, that's up to them. @rfcbot abstain |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
Meh, I share the concerns raised by @RalfJung, @BurntSushi, and @scottmcm. On one hand I want "obvious" things like this to make newtypes better. On the other, I'm not sure it meets that bar because many newtypes don't actually want this, and it takes up a big slice of linguistic space we could potentially redistribute to other meanings. Sometimes you want the inverse Personally I'd rather see a slightly more "maximalist vision" that this fits into as a first step. That could take the form of a more expansive future possibilities section that covers the situations I listed above (or reasons why we shouldn't cover them). EDIT: I could be convinced there are a bunch of newtypes out there where this derive is idiomatic and a good tradeoff. In my code it usually is not. One point I'll make in favor of the RFC is that this behavior seems like the most obvious one by far for |
I agree that it would be nice to have the other direction as well, but I would find it very surprising if I wonder if instead of using the existing derive attribute, it would make more sense to create a new attribute for a generalization of this, where if you have a generic trait with a single type paramater, like Alternatively, it could use a derive macro with a new name that doesn't correspond to an existing trait. |
I think this RFC got bogged down with newtypes too much. It's just one use-case, but this derive is a general tool, used to reduce boilerplate for trivial From impls. Nothing less, nothing more. Some other derives can also be semantically wrong, but that's not the compiler's/language's concern (IMO). I understand the desire to also explore the other direction of the impl, but I'd like to keep the RFC minimal, as I think that it stands on its own and it is useful even without the other direction (otherwise I wouldn't propose it). The only reason why it should be necessary to decide what to do about the other direction in this RFC would IMO be if the proposed derive was not forward compatible. Do you see that as being the case? I think that based on the current derives, we can't "afford" for |
Single-field types are newtypes, so I don't see how this derive can be used for anything else? |
I wouldn't classify it in that way. The newtype pattern is a (design) pattern; these always describe some motivation for why they are being used. For the newtype pattern, the motivation is to introduce a new element in the type system that is backed by an existing data type, to distinguish them in the type system even if they have the same in-memory representation. Potentially you can also add additional invariants to the inner data type (I'd argue these are two different patterns with different use-cases, which is also a part of the opposing viewpoints in this RFC, where some people use newtypes for the invariants, but others use it only for adding a new type system element). But you can have code that looks in that exact same way (single-field struct), but it does not uphold the pattern, because it has a different motivation. This is also known from the classical GoF patterns, where multiple patterns can have exactly the same code signature, but different motivation for why they are being used. Sometimes a single-field struct is just a single-field struct, and you are not creating it for the reason of introducing a new element in the type system. For example, recently I had a use-case where I had a struct with a lot of fields, and I wanted to have the option to use the struct literal constructor to initialize these fields (to avoid a struct Config { paramA, paramB, paramC }
struct ReadOnlyConfig(Config);
let config = ReadOnlyConfig::from(Config { paramA: 1, paramB: 2, paramC: 3 })); The motivation here was not to distinguish these two types in the type system, nor to add any invariants. I just wanted to combine struct literal initialization with private access to the fields. (btw: |
I would argue in that case you want to distinguish But anyway, the real question is whether the way you use newtypes is common enough to justify this extension. The way I use the language, I don't think I'd use this feature more than once in a blue moon, and others have raised similar concerns. In the RFC, you give evidence for this:
I don't have a good sense for how much potential use is "enough" to justify this as a core language feature. I admit this is already a lot more than I expected.
Yeah, that's a good point. If we do eventually want to have a derive for the other direction (which I'd find a lot more useful), it would probably be quite confusing to call that |
This is overselling it a bit, I think. Even t-lang said that they don't consider this to be a big lang deal, and it's mostly "just" a t-libs-api concern. I view this as filling a gap in the combination of two existing features (derive and built-in traits), rather than introducing a new feature. Anyway, the FCP has started, if someone wants to register formal complain, there's still time :) |
Yeah fair, a core library (as in, libcore) feature would have been more accurate. :) |
The final comment period, with a disposition to merge, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. This will be merged soon. |
Oops, I meant to register a concern here to reflect discussion above. Not sure if it's too late, but I'll try anyway. @rfcbot concern from-impl-direction-grander-picture |
Hmm, I wonder if this won't confuse the hell out of rfcbot :) No problem with the timing though, we can just delay merge or restart the FCP later. Could you please clarify a bit on how would you like the end state of this RFC to look like? This RFC describes a complete (well, I hope, it looks simple, but maybe we'll run into some impl issues) design for From in direction A. I could add more thoughts about From in direction B to it, but unless we determine a final design that will incorporate both directions, it will be just hand-waving that might never get implemented or that will get changed anyway once/if we create a RFC for direction B. So I don't see much point in doing that just for the sake of it. From my point of view, we can either:
Please let me know if you want me to do 2), or if you want something else :) |
As I think about it more, it seems clear to me that we should support both This vision seems small, clear, and interrelated enough that I would prefer to decide on it together, but this RFC is far along enough that it doesn't seem worth changing the scope. Given that, a future possibilities section for
Just noticed this and now I have a procedural concern: I don't see any reason an RFC should leave such a fundamental question unresolved! The RFC should resolve it instead of deferring more fundamental design discussion to stabilization. I think it should pick the direction it's written with; in other words, I think this section should be removed (EDIT: or moved to "rationale and alternatives"). |
Do you want me to keep "Generating From in the other direction" in the "Rationale and alternatives" section, or also remove it from there? |
…m unresolved questions to `Rationale and alternatives`
Moved the discussion of the |
Previously discussed as Pre-RFC on IRLO.
Rendered