-
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
Type-changing struct update syntax #2528
Type-changing struct update syntax #2528
Conversation
Ah, it needs to be the same type constructor? That makes me far less scared 🙂 The other thing this makes me ponder is that this doubles-down on the "move the other fields" interpretation. I would like to soundly support FRU on non_exhaustive structs and those with private fields at some point, where the "replace the fields on the updatee" interpretation works better. |
I'd like Rust to support private fields in struct update syntax, too, but I'd prefer to handle that feature separately from this RFC. This RFC is fairly limited in scope, and there aren't many alternatives if we want to support changing generic type parameters. (The primary question is the set of type inference rules.) If we are concerned about this RFC causing problems for private field support, we could delay discussion of this RFC until we discuss how to add support for private fields, or we could work on a combined RFC for both features. However, it seems like the changes in this RFC won't significantly1 affect future discussion about supporting private fields. If we had to choose between supporting private fields and supporting type changes, I'd choose to support type changes anyway: If we don't add support for private fields to struct update syntax, then updating a struct with private fields can still be written as let mut base = base_expr;
base.field1 = expr1;
base.field2 = expr2;
base In contrast, there is no compact equivalent for type-changing updates without this RFC, because assignments to the fields can't change the type of let base = base_expr;
Struct {
field1: expr1,
field2: expr2,
common_field1: base.common_expr1,
common_field2: base.common_expr2,
common_field3: base.common_expr3,
common_field4: base.common_expr4,
} Regarding the "always consume the base instance" approach for supporting private fields (which is a generalization of the "replace the fields on the updatee" interpretation), specifically: This approach does provide a nice, simple way to add support for private fields to current Rust, although it's a breaking change in the special case that all of the moved fields implement
|
The directions are opposite, but both seems useful. |
This seems reasonable to me. 👍 |
Because bikesheds are fun... Foo { a: 1, b: false, ..move old } ... which I see now was listed as an alternative on the linked proto RFC Or, relying on some current behavior of ye olde identity function (a.k.a. braces)... Foo { a: 1, b: false, ..{ old } } |
It may appear weird that I don't think this is something we can or ever want to change, unless we ever end up with something like C++'s |
I like the intent of this proposal, and I really appreciate the amount of thought that has gone into the repercussions of this change, even in its relatively constrained form. (The title of this RFC makes one think it is proposing something far more general than what is actually here.) I will admit a slight discomfort with the proposed type-inference rules. Specifically the text:
This has an aura of back-tracking to it that gives me pause. But I'm probably just over-reacting. It certainly seems like this could be worth prototyping. |
What has to happen for this to move forward? |
As an aside, you're always doing a change |
We talked about this in the lang team meeting today. We're generally in favor of this, as long as it's limited to another struct of the same type with different generic parameters (e.g. The lang team needs to review some of the discussion of type inference details, but pending that, we'd be in favor of merging. |
@rfcbot merge |
Team member @joshtriplett 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! See this document for info about what commands tagged team members can give me. |
@rfcbot concern review-inference-strategy I'd like to review the inference strategy, but I'm in favor of the overall goal of the RFC. |
One thing I'm pondering that I'm not sure yet whether it raises to the level of a concern: This is very much doubling down on We've talked many times about wanting to find a way to get FRU (or something like it) working with † or perhaps something even more nuanced to preserve the unused values, such as { let mut temp = other; other.a = mem::replace(&mut temp.a, 4); temp } |
I think it makes sense to support both this and struct update syntax on non-exhaustive structs. That would mean that struct update syntax could no longer be just syntax sugar, but I don't see why it has to be. |
However, the types of the fields in the updated instance that are not | ||
explicitly listed (i.e. those that are moved with the `..` syntax) must be | ||
subtypes of the corresponding fields in the base instance, and all of the | ||
fields must be visible ([RFC 736]). In other words, the types of fields that |
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.
Yeah so I guess per @scottmcm's point, maybe we should roll back this rule. I guess it's worth deciding that separately.
OK, so I reviewed the text on type inference. I think that what I was expecting was something like this:
I realize now that this is technically a breaking change. We should write out some of the examples. In particular, it can affect the coercion rules. The RFC already cited integer fallback as one possible interaction, but another would be something like: struct Foo<T> { t: T }
fn main() {
let foo = Foo::<&[u32]> { t: &[22, 44] };
let bar = Foo { t: &[44, 66], ..foo };
} The type of |
In any case, I think that I would want to update the text regarding inference to something like what I sketched above -- but @jturner314 maybe there is a reason that this logic doesn't work? |
Let me first state that I'm not very familiar with the intricacies of how Rust's type inference works; I'm working from my limited knowledge of the type inference from my usage of the language and a little reading of the compiler docs. I think the discussion of type inference in the RFC would ideally be refined with the help of someone more knowledgeable than me. @nikomatsakis I'm not sure I understand what you mean by the third bullet point; do you mean that the types of the explicitly listed fields in the updated instance would be required to be subtypes of the types of the corresponding fields in the base instance? I'm proposing in the RFC that type inference would try something similar to that first, but if that's not possible, to infer the types of the explicitly named fields independently of the base instance. It's worth noting that the following statements are not equivalent. I believe statement (1) implies statement (2), but I'm not sure if that's the case. Statement (2) definitely doesn't imply statement (1).
Rust currently requires (1) to be true when using struct update syntax. For example, the following raises a "mismatched types" error in current Rust, even though the types of the fields in struct Struct<T: Trait> {
field1: T::Assoc1,
field2: T::Assoc2,
}
trait Trait {
type Assoc1;
type Assoc2;
}
struct T1;
impl Trait for T1 {
type Assoc1 = i32;
type Assoc2 = f64;
}
struct T2;
impl Trait for T2 {
type Assoc1 = i32;
type Assoc2 = f64;
}
fn main() {
let base: Struct<T1> = Struct { field1: 1, field2: 2. };
let updated: Struct<T2> = Struct { field1: 3, ..base };
} The proposal in the RFC uses a two-stage procedure to infer the types: the first stage tries to apply statement (1), and the fallback second stage infers the types of the explicitly named fields independently, like this:
A backwards-compatible three-stage procedure starting with statement (2) instead of statement (1) could work like this:
Note that step 6 is necessary for backwards compatibility: In my example above, consider replacing The procedures listed above are somewhat simplified, since there may be multiple expressions of struct update syntax within a single inference context. The RFC says that in this case, "[The first stage] is evaluated simultaneously for all instances of struct update syntax within the inference context, and conflicts between applications of [the first stage] should result in compilation errors." Fwiw, my personal favorite option for type inference is the "Combination of always independently inferring types of explicitly listed fields and disabling The only reason why I didn't choose that as the proposed option in the RFC is the backwards compatibility issue (although last I checked, Rust reserves the right to make breaking changes to type inference). I wrote the RFC to narrowly provide backwards compatibility, while independently inferring types when backwards compatibility is not an issue. |
…1289) This PR aims to remove a lot of initializer boilerplate code by adopting the`struct update syntax. If the [RFC 2528] gets merged and implemented, we can remove more. 😸 [RFC 2528]: rust-lang/rfcs#2528
Okay, great. I've invited you as a collaborator on jturner314/rust-rfcs so that you can push to the PR branch ( |
…1289) This backports #1289 from `master`. This PR aims to remove a lot of initializer boilerplate code by adopting the`struct update syntax. If the [RFC 2528] gets merged and implemented, we can remove more. 😸 [RFC 2528]: rust-lang/rfcs#2528
…1289) This backports #1289 from `master`. This PR aims to remove a lot of initializer boilerplate code by adopting the`struct update syntax. If the [RFC 2528] gets merged and implemented, we can remove more. 😸 [RFC 2528]: rust-lang/rfcs#2528
@rfcbot resolve review-inference-strategy |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
I'm sorry this has taken so ridiculously long |
Happy to work with someone on implementing it, though! |
Will it be possible to have multiple bases later on? Since previously we can only have one type as base, now we can take a different type as base. |
I'm not sure what multiple base support would look like |
Like |
This RFC only discusses struct updates that change type parameters within one fixed but polymorphic type, not unrelated types with similarly named items, so this remains type controlled, not purely syntactic. Anything purely syntactic like you describe comes under structural records, which Rust removed early on, and interacts with many things, but you'll find plenty of discussion around that topic. |
I believe rust could support type changing updates with private fields via explicit visibility modifiers:
In this, we may alter our public field If you want a more fun example, then imagine silencing some fields, so that |
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. The RFC will be merged soon. |
Nominating for someone to actually merge :) |
On it. |
…okio-rs#1289) This backports tokio-rs#1289 from `master`. This PR aims to remove a lot of initializer boilerplate code by adopting the`struct update syntax. If the [RFC 2528] gets merged and implemented, we can remove more. 😸 [RFC 2528]: rust-lang/rfcs#2528
Extend struct update syntax (a.k.a. functional record update (FRU)) to support instances of the same struct that have different types due to generic type or lifetime parameters.
Rendered.
See also #1975 and rust-lang/rust#47741.