RFC: enable derive(From) for single-field structs by Kobzol · Pull Request #3809 · rust-lang/rfcs (original) (raw)

Conversation

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters

[ Show hidden characters]({{ revealButtonHref }})

@Kobzol

tgross35, juntyr, davidhewitt, attila-lin, cyrgani, Jules-Bertholet, Evian-Zhang, Expurple, andytesti, zachs18, and 69 more reacted with thumbs up emoji fmease reacted with thumbs down emoji fmease reacted with confused emoji kennytm, skogseth, aznhe21, coolreader18, metasim, teor2345, bbb651, attila-lin, tkr-sh, joseph-gio, and 25 more reacted with heart emoji

@Kobzol

@jieyouxu jieyouxu added the T-lang

Relevant to the language team, which will review and decide on the RFC.

label

May 6, 2025

@clarfonthey

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 Default impls on the extra fields would not be needed.

Kobzol, Expurple, teor2345, mcobzarenco, izik1, safetycoomer, bushrat011899, ksgk1, stephen-h-d, palozano, and 2 more reacted with thumbs up emoji

@Kobzol

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 Default impls on the extra fields would not be needed.

PhantomData implements Default unconditionally for any T, so that would be fine. I agree it's best to leave it for "later" though.

Good point with the generics, I'll add a mention to the RFC.

@kennytm

"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 for Foo { fn from(a: usize) -> Self { Self { a, .. } } }

OTOH we may still want the #[from] in the above case for uniformity if we want to support #[derive(From)] on all fields being defaulted

#[derive(From, Default)] struct Foo2 { #[from] // <-- perhaps still want this a: u128 = 1, b: u16 = 8080, }

Kobzol, clarfonthey, zachs18, skogseth, bluebear94, joseph-gio, safetycoomer, ksgk1, m4rch3n1ng, RalfJung, and 3 more reacted with thumbs up emoji

@Kobzol

shepmaster

@Kobzol @shepmaster

Co-authored-by: Jake Goulding shepmaster@mac.com

@nielsle

The crate named derive-new creates a new constructor, and it has several fancy options.

But perhaps this functionality belongs in a third party crate rather than the standard library.

teor2345

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

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

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

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.

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

Understood! But my comment was more about the confusing semantics, than the exact name of the impl'd trait.

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.

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

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.

@Kobzol

The crate named derive-new creates a new constructor, and it has several fancy options.

But perhaps this functionality belongs in a third party crate rather than the standard library.

While new constructors are pretty common, I would say that it's too opinionated for it to be included in the stdlib, as it's not a standard (library) trait like From is. In any case, that would be a separate RFC :)

@Kobzol @teor2345

Co-authored-by: teor teor@riseup.net

@joshtriplett

I think this proposal makes sense as written, and it's simple and straightforward.

Should we allow derive(From) on single-field structs, to impl From<FieldType> for Struct?

@rfcbot merge

@rfcbot

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.
See this document for info about what commands tagged team members can give me.

@matthieu-m

A better alternative might be to support generating the other direction in the future through something like #[derive(Into)].

I very much encourage distinct syntax for distinct directions.

In particular, as noted in the Email(String) example, newtype wrappers are regularly used to enforce invariants, in which case the #[derive(From)] implementation is outright wrong.

On the other hand, going back to the inner type should never violate any invariant, and therefore #[derive(Into)] makes perfect sense for Email.

@scottmcm

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 From might be wrong", because correctness is up to the caller to do properly (like how today derive(Clone) might be wrong if you're holding pointers or how derive(Hash) might be wrong if you implemented PartialEq manually.) I think in a hypothetical future with unsafe fields then perhaps this should fail, but that's not something this RFC would need to mention because there's no accepted RFC for that yet. (Though if it wanted to discuss that as future possibilities that would be nice-to-have.)

@traviscross

As a comparable, we handled RFC #3107 (#[default]) as a dual lang/libs-api FCP. Probably I think that makes sense here as well (e.g., it maybe leads to #[from]). This perhaps touches on general "flavor of the language" matters, and also raises for us questions about whether we might like to solve the motivation here in some other language-directed way.

@rfcbot fcp cancel
@rustbot labels +T-libs-api

@rfcbot

@rustbot rustbot added the T-libs-api

Relevant to the library API team, which will review and decide on the RFC.

label

Jun 12, 2025

@traviscross

@rfcbot fcp merge

(And I'll recheck the box for Josh.)

@rfcbot

@Kobzol

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:

  1. Determine that #[derive(From)] does the obvious thing, and we don't see a way how it could not be forward-compatible, so we accept it and let it progress without blocking on the other direction. Or:
  2. Determine a design for both directions at once, so that we are (almost) sure that there won't be any forward compatibility issues with the other direction. If we find that direction B is too hard to resolve or has too many trade-offs, we can go back to 1), or decide that we don't want to go forward with neither A nor B.

Please let me know if you want me to do 2), or if you want something else :)

@tmandry

As I think about it more, it seems clear to me that we should support both derive(From) and derive(Into). The Into derive should actually write an impl of From in the inverse direction. (This is a strict superset of an Into impl in the expected direction unless you target an MSRV prior to 1.41, in which case you need to write the impl manually anyway.)

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 derive(Into) would be enough to resolve my part of the concern.

Unresolved questions
Direction of the From impl

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

@Kobzol

Do you want me to keep "Generating From in the other direction" in the "Rationale and alternatives" section, or also remove it from there?

@Kobzol

…m unresolved questions to Rationale and alternatives

@Kobzol

Moved the discussion of the From direction to Rationale and alternatives, and briefly mentioned #[derive(Into)] as a future possibility.

@Kobzol

@BurntSushi Hey, could you please clarify if my changes have resolved your concerns, or if not, what would you like to see here? Thanks!

@BurntSushi

@rfcbot resolve from-impl-direction-grander-picture

Thank you for updating the RFC. I'm on board with moving this forward. But I suspect we will need to decide on whether and if we are to add derive(Into) for the other direction before stabilization. And in particular, I am suspicious of derive(Into) given that it won't be adding an implementation of the Into trait. And if that isn't called derive(Into), then I predict there will be a naming conundrum. Anyway, we don't have to resolve that here, but I am skeptical of this being stabilized before we have a more complete picture of the "maximalist" version of this RFC.

@rfcbot

🔔 This is now entering its final comment period, as per the review above. 🔔

@kennytm

I am suspicious of derive(Into) given that it won't be adding an implementation of the Into trait.

again if #[derive(Into)] does not actually impl Into for T is considered a problem then with the same argument #[derive(CoercePointee)] should be kicked out from language because there is no impl CoercePointee for T either (no such trait existed). The fact that #[derive(CoercePointee)] is stabilizing (rust-lang/rust#133820) shows that the concern about macro name differing from the actual impl declaration is already dismissed.

@RalfJung

I am suspicious of derive(Into) given that it won't be adding an implementation of the Into trait.

again if #[derive(Into)] does not actually impl Into for T is considered a problem then with the same argument #[derive(CoercePointee)] should be kicked out from language because there is no impl CoercePointee for T either (no such trait existed). The fact that #[derive(CoercePointee)] is stabilizing (rust-lang/rust#133820) shows that the concern about macro name differing from the actual impl declaration is already dismissed.

IMO it's a quite different situation when there is no trait with the name of the macro. With derive(CoercePointee) there is no possibility of confusion.

@programmerjake

if you name it #[derive(FromSelf)] or similar instead of #[derive(Into)], that could fix the naming issue

@Kobzol

Hmm, the FCP started on July 19, but still hasn't finished yet. The period is 10 days, right? Perhaps rfcbot is stuck on this PR?

@traviscross

@Skgland

Probably confused as the FCP already completed #3809 (comment) before the concern was added and then later resolved again.

This was referenced

Aug 4, 2025

@traviscross

@traviscross

@traviscross

jhpratt added a commit to jhpratt/rust that referenced this pull request

Aug 15, 2025

@jhpratt

Implement #[derive(From)]

Implements the #[derive(From)] feature (tracking issue, RFC).

It allows deriving the From impl on structs and tuple structs with exactly one field. Some implementation notes:

rust-timer added a commit to rust-lang/rust that referenced this pull request

Aug 16, 2025

@rust-timer

Rollup merge of #144922 - Kobzol:derive-from, r=nnethercote

Implement #[derive(From)]

Implements the #[derive(From)] feature (tracking issue, RFC).

It allows deriving the From impl on structs and tuple structs with exactly one field. Some implementation notes:

github-actions bot pushed a commit to model-checking/verify-rust-std that referenced this pull request

Aug 18, 2025

@jhpratt

Implement #[derive(From)]

Implements the #[derive(From)] feature (tracking issue, RFC).

It allows deriving the From impl on structs and tuple structs with exactly one field. Some implementation notes:

Reviewers

@kennytm kennytm kennytm left review comments

@joshtriplett joshtriplett joshtriplett left review comments

@shepmaster shepmaster shepmaster left review comments

@RalfJung RalfJung RalfJung left review comments

+3 more reviewers

@teohhanhui teohhanhui teohhanhui left review comments

@tmccombs tmccombs tmccombs left review comments

@teor2345 teor2345 teor2345 left review comments

Reviewers whose approvals may not affect merge requirements

Labels