RFC: Foo { .. } pattern matches non-struct types#3753
Conversation
|
a related pet peeve: you can use |
|
I'm against this change. A pattern is generally an inverse of an expression with the same syntax — I'm inclined to say that having the intuition of "if I can match it with a pattern, then I can create the value with a similar syntax" is good, and so breaking it is not so good. I might have had a different opinion be this a proposal for a language with a good server story. But since rust's semver story is quite bad, I don't think this small improvement can justify the special case. This is not to say we shouldn't improve rust's semver story. But in this particular case the downsides outweigh the improvement in an edge case. |
|
|
||
| * How much code in the wild currently uses patterns like `Foo { .. }` ? | ||
| * What was the original reason that the pattern `Foo { .. }` matches all | ||
| structs, and not just tuple-like structs? |
There was a problem hiding this comment.
This is because all structures are just that, structures.
Unit structures are just a sugar for a struct with a constant:
struct Unit;
// <=>
struct Unit {}
const Unit: Unit = Unit {};Similarly tuple structs are just a sugar for a struct with fields named as integers, a function, and a pattern (these are not expressible in surface level rust, but still):
struct Tuple(u8);
// <=>
struct Tuple { 0: u8 }
fn Tuple(_0: u8) -> Tuple { Tuple { 0: _0 } }
pattern Tuple(_0) = Tuple { 0: _0 }So, for most intents and purposes all kinds of structs are the same.
(also this probably has a typo, I assume it should have said "not just named structs"?)
There was a problem hiding this comment.
For more, see https://rust-lang.github.io/rfcs/1506-adt-kinds.html.
There was a problem hiding this comment.
After reading RFC 1506, I'm still rather confused on the motivation. It seems that the reason that a pattern like Tuple { 0: x } has the same meaning as Tuple(x) is for... macros? Am I missing something?
This comment was marked as off-topic.
This comment was marked as off-topic.
|
There is a benefit with this change other than semver compatibility - it can be useful to have an explicit type in a pattern instead of match something {
SomeStruct(SomeEnum { .. }, ....) => .., // future-proof: it's important that SomeEnum exists here
......instead I decided to do: match something {
SomeStruct(val, ....) => {
let _: SomeEnum = val; // future-proof: it's important that SomeEnum exists here
... |
|
@camsteffen IMO it would be better to have explicit syntax for annotating types in patterns, if that's the goal. |
| ``` | ||
|
|
||
| To eliminate this semver hazard, this RFC proposes that the pattern `Foo { .. }` | ||
| should match values of any type named `Foo`, not just structs. |
There was a problem hiding this comment.
This should also mention motivation for macros generating match statements where they want to verify a user-passed type.
There was a problem hiding this comment.
Can't a match arm like this already be generated?
ref foo_val => {
let _: &Foo = foo_val;
}| * As an alternative, we could deprecate the pattern `Foo { .. }` (either in all | ||
| cases, or only in cases where `Foo` has no public fields). We could then | ||
| potentially remove this pattern from the language in a future edition. |
There was a problem hiding this comment.
What are the use cases for using the Foo { .. } pattern when Foo is a struct with only private fields? If we were to start linting against that, what would we be losing?
E.g.:
//~^ WARN matching against structs with only private fields is fragile
//~| NOTE this code would break if `Foo` became an `enum` or a `union`
This general guideline is nice, but it doesn’t really apply here.
So we should work on making it better. Every little improvement will save some people some amount of pain when upgrading. This doesn’t have to be all-or-nothing. |
Right, my brain is already trained to think of it that way because of tuple structs. It's like "I'm telling the computer that I am specifying a type and not declaring a binding". |
|
For many years you could write |
this feature is called type ascription. |
|
I implemented this RFC: rust-lang/rust#159295 |
|
Lang-nominating, as @Jules-Bertholet requested T-lang to have a look at it to make a decision to accept or reject at #t-lang > Any chance for a review of RFC 3753? |
|
Related discussion: #t-lang > type ascription *in patterns* |
|
I think the RFC should explicitly say, in the top summary section, that this is not being added because we think the pattern is a thing people should write, just that it's a pattern we allow people to write and thus we need this to make sure it isn't a breaking change for crate maintainers. I also think we should lint against such a pattern. But after further consideration, 👍 for doing this to allow for backwards-compatible library crate evolution. |
| irrefutable pattern that matches against any value of that type. This applies | ||
| even if that type is not a struct type (e.g., it might be an enum type, or it | ||
| might be a type alias, etc.) |
There was a problem hiding this comment.
Is the expectation that I can also write @ u32 { .. }? Is it really any other type?
There was a problem hiding this comment.
It looks kinda wonky, but yes. I would personally find it to be weirder if I can do this for enums but not i32.
|
cc @obi1kenobi (cargo-semver-checks) |
|
We talked about this in the lang call at some length without specific resolution. Mostly people were a bit conflicted. For my part, I see the motivation: allowing upstreams to change the item-kind of a type without breaking downstreams who might have relied on this syntax (and there aren't always better options for type ascription). That almost brought me to Given that, I'm just not sure the motivation here is strong enough. It seems OK to me to say that it's a breaking change to move from a struct to something else — for now, at least. Maybe later we could ensure we have better ways to ascribe the type in all cases and then move toward disallowing the |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
|
To get some data about how widespread the problematic |
|
Similar to what TC said above, I'm torn on this. I agree that library evolution is important, but also this is just really icky in the implications. We'd never even consider allowing So to me it all hinges on the reasonableness of doing this in downstream code in the first place. Is this actually useful? If there's no reason to write it and we lint it on all editions, I might just add this to the "well yes technically it can be source breaking but we deem it fine", the same as we do with "well yes, technically any inherent method addition can be source breaking but we deem it fine anyway" and various other such things. Or maybe we need a new feature -- type ascriptions in patterns anyone? -- and this is evidence of that, rather than a "we should add something icky that we don't want anyone to use". |
|
I think to the extent this is a real problem we should provide a solution. I'm curious to see the crater results to quantify that problem, particularly the cases where the struct is defined in another crate. As for the solution, I agree with @scottmcm that we might be able to get away with linting on the pattern and reducing any potential breakage such that it's "morally okay" to do without a semver bump. That's assuming we can avoid linting on any uses we deem legitimate. I'm open to this RFC as the solution if the lint doesn't seem so good. |
View all comments
Special case struct patterns containing only a rest pattern (e.g.,
Foo { .. }) so that they can match values of any type with the appropriate name, not just structs (e.g., it could match anenum Foovalue). This is done so that structs containing only private fields can be changed to other types without breaking backwards compatibility.Prior discussion on IRLO
Rendered