Skip to content
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: Foo { .. } pattern matches non-struct types #3753

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

theemathas
Copy link

@theemathas theemathas commented Dec 31, 2024

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 an enum Foo value). 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

@programmerjake
Copy link
Member

a related pet peeve: you can use Ty { 0: abc, 1: def } syntax on tuple structs or type aliases of tuple structs but not on type aliases of actual tuples, which makes macros more annoying

@ehuss ehuss added the T-lang Relevant to the language team, which will review and decide on the RFC. label Dec 31, 2024
@WaffleLapkin
Copy link
Member

I'm against this change.

A pattern is generally an inverse of an expression with the same syntax — & expression creates a reference, & pattern dereference it; S { a } expression creates a struct S from pieces, S { a } pattern destructures it; ... (an exception is ref vs *). This proposal muddies this concept, since it allows matching an option with Option { .. }, while an option can't be constructed with Option { ... } (... being a placeholder for literally anything).

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?
Copy link
Member

@WaffleLapkin WaffleLapkin Jan 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@obi1kenobi

This comment was marked as off-topic.

@camsteffen
Copy link

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 _, either for readability or to guard yourself against refactors where that type might change. I actually ran into a case recently where I wanted to do this:

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

@WaffleLapkin
Copy link
Member

@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.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also mention motivation for macros generating match statements where they want to verify a user-passed type.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't a match arm like this already be generated?

ref foo_val => {
    let _: &Foo = foo_val;
}

Comment on lines +105 to +107
* 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.
Copy link
Contributor

@traviscross traviscross Jan 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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`

@theemathas
Copy link
Author

theemathas commented Jan 4, 2025

I did a cursory search manually for code on github that uses a pattern like Foo { .. }. It appears that there is some amount of people that uses such a pattern, probably to make sure that the value has the expected type. Here are some examples.

Code that uses this pattern on a struct with all-private fields from an external crate:

Code that uses this pattern on a struct with public fields from an external crate:

Code that uses this pattern on a struct defined in the same crate:

It seems to me that there is at least a decent number of rust users, including in some high-profile crates, that consider the Foo { .. } pattern to be at least worth using.

Is there a way to programmatically find uses of such a pattern?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants