-
Notifications
You must be signed in to change notification settings - Fork 1.6k
RFC: Foo { .. }
pattern matches non-struct types
#3753
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?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
- Feature Name: `rest_pattern_matches_non_struct` | ||
- Start Date: 2024-12-31 | ||
- RFC PR: [rust-lang/rfcs#3753](https://github.com/rust-lang/rfcs/pull/3753) | ||
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
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. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
It is common for a library's API to have a public struct with all-private | ||
fields, with the intention of making the type opaque, hiding its contents as | ||
implementation details. For example: | ||
```rust | ||
// A library's initial API | ||
pub struct Foo { | ||
inner: FooInner, | ||
} | ||
enum FooInner { | ||
A, | ||
B, | ||
} | ||
``` | ||
|
||
In a later version, the library might want to expose the internals of the API. | ||
For example: | ||
```rust | ||
// The library's API is later changed to this. | ||
pub enum Foo { | ||
A, | ||
B, | ||
} | ||
``` | ||
|
||
Intuitively, one might think that this change is backwards-compatible. However, | ||
this is technically not the case, since client code might match the `Foo` type | ||
with a rest pattern (`Foo { .. }`), which currently only matches structs, and | ||
not enums or other types. | ||
```rust | ||
// Client code that uses the library | ||
// Works with the initial API, but doesn't work with the later API. | ||
fn do_something(x: Foo) { | ||
match x { | ||
Foo { .. } => {} | ||
} | ||
} | ||
``` | ||
|
||
To eliminate this semver hazard, this RFC proposes that the pattern `Foo { .. }` | ||
should match values of any type named `Foo`, not just structs. | ||
|
||
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
As a special case, a struct pattern which contain only a rest pattern, and no | ||
other fields, can match with any value of the appropriate type, even if it is | ||
not a struct. | ||
|
||
For example, the pattern `Foo { .. }` can match with any value that have type | ||
`Foo`, even if `Foo` is not a struct (e.g., it might be an enum). | ||
|
||
# Reference-level explanation | ||
[reference-level-explanation]: #reference-level-explanation | ||
|
||
(The following text is appended to the section on [struct | ||
patterns](https://doc.rust-lang.org/stable/reference/patterns.html#struct-patterns) | ||
in the reference.) | ||
|
||
As a special case, if a struct pattern contains only `..` as the fields, and the | ||
path refers to a type (as opposed to an enum variant), then the pattern is an | ||
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.) | ||
|
||
For example, the pattern `foo::Bar::<Baz> { .. }` can match any value of the | ||
type `foo::Bar<Baz>` (even if this type is not a struct type), or match a value | ||
of the type `foo<Baz>` that contains the enum variant `Bar`. | ||
|
||
Formally, this special case applies to the following syntax: *PathInExpression* | ||
`{` *StructPatternEtCetera* `}` | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
This adds a complication to Rust. | ||
|
||
# Rationale and alternatives | ||
[rationale-and-alternatives]: #rationale-and-alternatives | ||
|
||
* This special case is only applied to `Foo { .. }`, and not `Foo(..)`. | ||
* This is because, currently, the pattern `Foo(..)` can only match a tuple | ||
struct whose fields are all public, so the `Foo(..)` pattern does not pose a | ||
semver hazard. | ||
* On the other hand, the pattern `Foo { .. }` currently matches any struct | ||
named `Foo`, including tuple structs, and including type aliases that refer to | ||
structs. This makes the meaning of the pattern `Foo { .. }` already similar to | ||
"match any type named `Foo`". | ||
* 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. | ||
Comment on lines
+105
to
+107
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are the use cases for using the 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 unfortunately doesn't fix the semver hazard, due to code in older | ||
editions existing. Additionally, this might be an edge case that macros | ||
would have to deal with. | ||
* As an alternative, we could have an attribute that marks a type as completely | ||
opaque, and therefore making it not able to be matched with the pattern `Foo { | ||
.. }`. | ||
* Most users are likely to forget to apply this attribute. We could change the | ||
default over an edition, making a struct with no public fields implicitly | ||
opaque, but this special case seems rather weird and confusing. | ||
|
||
# Prior art | ||
[prior-art]: #prior-art | ||
|
||
`cargo-semver-checks` has [a | ||
lint](https://github.com/obi1kenobi/cargo-semver-checks/issues/954) that checks | ||
specifically if a struct containing only private fields is changed to a | ||
different type. | ||
|
||
# Unresolved questions | ||
[unresolved-questions]: #unresolved-questions | ||
|
||
* 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. Choose a reason for hiding this commentThe 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"?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For more, see https://rust-lang.github.io/rfcs/1506-adt-kinds.html. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After reading RFC 1506, I'm still rather confused on the motivation. It seems that the reason that a pattern like |
||
* Are there any other ways in the language (other than the `Foo { .. }` pattern) | ||
for client code to depend on a type being specifically a struct? | ||
|
||
# Future possibilities | ||
[future-possibilities]: #future-possibilities | ||
|
||
N/A |
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.
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.
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?