From 840f5ed96fcb69489a807c9e2e069350323a9d28 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 17 Aug 2022 12:28:31 +0200 Subject: [PATCH 01/18] Statics in patterns RFC --- text/0000-static-in-pattern.md | 264 +++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 text/0000-static-in-pattern.md diff --git a/text/0000-static-in-pattern.md b/text/0000-static-in-pattern.md new file mode 100644 index 00000000000..d39818029c8 --- /dev/null +++ b/text/0000-static-in-pattern.md @@ -0,0 +1,264 @@ +- Feature Name: `static_in_pattern` +- Start Date: 2022-08-17 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary + +[summary]: #summary + +Allow referencing non-`mut` `static`s in pattern matches wherever referencing a `const` of the same type would be allowed. + +# Motivation + +[motivation]: #motivation + +Rust pattern matches compare a scrutinee against compile-time information. Rust generally doesn't allow patterns to depend on runtime information; that is relegated to match guards. However, Rust currently also prevents patterns from depending on *link-time* information, specifically statics from `extern` blocks. + +I encountered this restriction while trying to port the Rust standard library to [cosmopolitan libc](https://justine.lol/cosmopolitan/index.html). Cosmopolitan provides an API that mostly matches POSIX, with one major exception: constants like `ENOSYS` and `EINVAL`, which on most platforms are defined as C `#define`s (equivalent to Rust `const`s), are instead provided as C `const`s (equivalent to Rust non-`mut` `static`s). + +```rust +// libc crate + +cfg_if! { + if #[cfg(target_env = "cosmopolitan")] { + extern "C" { + pub static EINVAL: i32; + pub static ENOSYS: i32; + pub static ENOENT: i32; + } + } else { + pub const EINVAL: i32 = 42; + pub const ENOSYS: i32 = 43; + pub const ENOENT: i32 = 44; + } +} + +// stdlib code + +use libc::*; + +fn process_error(error_code: i32) { + match error_code { + // Compiler throws error EO530 on Cosmopolitan, + // because `static`s can't be used in patterns, only `const`s + EINVAL => do_stuff(), + ENOSYS => panic!("oh noes"), + ENOENT => make_it_work(), + _ => do_different_stuff(), + } +} +``` + +Because Rust patterns don't support statics, all the `match` expressions in the standard library that refer to POSIX constants would currently need to be rewritten to accommodate Cosmopolitan. + +```rust +// stdlib code adapted for cosmopolitan + +use libc::*; + +fn process_error(error_code: i32) { + if error_code == EINVAL { + do_stuff(); + } else if error_code == ENOSYS { + panic!("oh noes"); + } else if error_code == ENOENT { + make_it_work(); + } else { + do_different_stuff(); + } +} +``` + +Needless to say, this is unlikely to ever be upstreamed. Allowing statics in patterns would solve this use-case much more cleanly. + +# Guide-level explanation + +[guide-level-explanation]: #guide-level-explanation + +Rust patterns can refer to constants. + +```rust +const EVERYTHING: i32 = 42; + +fn foo(scrutinee: i32) { + match scrutinee { + EVERYTHING => println!("have all of it"), + _ => println!("need moar"), + } +} +``` + +With this feature, they can refer to statics as well. + +```rust +static EVERYTHING: i32 = 42; + +fn foo(scrutinee: i32) { + match scrutinee { + EVERYTHING => println!("have all of it"), + _ => println!("need moar"), + } +} +``` + +Mutable statics are not allowed, however. Patterns can't reference information that can change at runtime, and also can't be `unsafe`. + +```rust + +static mut EVERYTHING: i32 = 42; + +fn foo(scrutinee: i32) { + match scrutinee { + // ERROR can't refer to mutable statics in patterns + /* EVERYTHING => println!("have all of it"), */ + _ => println!("need moar"), + } +} +``` + +Statics from `extern` blocks are allowed, but they must be marked as trusted using the (not-yet-implemented) [trusted external statics](https://github.com/rust-lang/lang-team/issues/149) feature. + +```rust +extern "C" { + #[unsafe(trusted_extern)] + static EVERYTHING: i32; +} + +fn foo(scrutinee: i32) { + match scrutinee { + EVERYTHING => println!("have all of it"), + _ => println!("need moar"), + } +} +``` + +# Reference-level explanation + +[reference-level-explanation]: #reference-level-explanation + +For a static to be eligible for use in a pattern, it must: + +- not be marked `mut` +- not be marked `#[thread_local]` +- not come from an extern block, unless it is marked as safe to use with the [trusted external statics](https://github.com/rust-lang/lang-team/issues/149) feature +- have a type that satisfies the structural match rules, as described in [RFC 1445](1445-restrict-constants-in-patterns.md), but without any allowances for backward compatibility like there are for consts (e.g., floating point numbers in patterns) . These rules exclude all statics with interior mutability. + +Static patterns match exactly when a const pattern with a const of the same type and value would match. + +The values of statics are treated as opaque for reachability and exhaustiveness analysis. + +```rust +static TRUE: bool = true; +static FALSE: bool = false; + +fn foo(scrutinee: bool) { + match scrutinee { + TRUE | FALSE => println!("bar"), + + // The compiler will throw an error if you remove this branch; + // it is not allowed to look into the values of the statics + // to determine that it is unreachable. + _ => println!("baz"), + } +} +``` + +As an exception, when all safe values of a type are structurally equal, the compiler is allowed to see that the match will always succeed. + +```rust +// Not all `&()` are bitwise equal, +// but they are structurally equal, +// that is what matters. +static ONE_TRUE_VALUE: &() = &(); + +fn foo(scrutinee: &()) { + match scrutinee { + ONE_TRUE_VALUE => println!("only one branch"), + // No need for a wildcard. + // The above match always succeeds. + } +} +``` + +Visibility and `#[non_exhaustive]` can affect whether the compiler can tell that all values of the type are structurally equal. + +```rust +mod stuff { + #[derive(PartialEq, Eq)] + pub(super) struct PrivateZst(()); + + pub(super) static PRIVATE_ZST: PrivateZst = PrivateZst(()); +} + +fn foo(scrutinee: stuff::PrivateZst) { + match scrutinee { + stuff::PRIVATE_ZST => println!("secrets abound"), + // `stuff::PrivateZst` has a field that's not visible in this scope, + // so we can't tell that all values are equivalent. + // The wildcard branch is required. + _ => println!("incorrect password"), + } +} +``` + +```rust +// crate `stuff` +#[derive(PartialEq, Eq)] +#[non_exhaustive] +struct PrivateZst(); + +// main crate +extern crate stuff; + +fn foo(scrutinee: stuff::PrivateZst) { + match scrutinee { + stuff::PRIVATE_ZST => println!("secrets abound"), + // `stuff::PrivateZst` is marked `#[non_exhaustive]` + // and comes from an external crate, + // so we can't tell that all values are equivalent. + // The wildcard branch is required. + _ => println!("incorrect password"), + } +} +``` + +The examples above all use `match`, but statics would be allowed in all other language constructs that use patterns, including `let`, `if let`, and function parameters. However, as statics cannot be used in const contexts, static patterns are be unavailable there as well. + +# Drawbacks + +[drawbacks]: #drawbacks + +This change slightly weakens the rule that patterns can only rely on compile-time information. In addition, static patterns may have slightly worse performance than the equivalent constant patterns. + +# Rationale and alternatives + +[rationale-and-alternatives]: #rationale-and-alternatives + +The proposed rules around reachability and exhaustiveness checking are designed to ensure that changing the value of a static, or changing from a static defined in Rust to a trusted extern static, is never a breaking change. The special dispensations for types with a single value could be considered unnecessary, as matching on such a type is a pointless operation. However, the rules are not difficult to implement (I managed to do it myself, despite near-zero experience contributing to the compiler), and are arguably the most correct and least surprising semantics. + +Allowing unsafe-to-access statics in patterns (`static mut`s, untrusted `extern` statics, `#[thread_local]` statics) is another possibility. However, I believe this option to be unwise: + +- Rust generally has not allowed unsafe operations (like union field accesses) in pattern matches +- It's not clear where the `unsafe` keyword would go (within the pattern? around the whole `match` or `let`? what about patterns in function parameters?) +- it requires Rust to commit to and document, and users to understand, when exactly it is allowed to dereference the static when performing a pattern match + +As for not making this change at all, I believe this would be a loss for the language as it would lock out the use-cases described above. This is a very simple feature, it doesn't conflict with any other potential extensions, the behavior and syntax fit well with the rest of the language, and it is immediately understandable to anyone who is already familiar with matching on `const`s. + +# Prior art + +[prior-art]: #prior-art + +As far as I am aware, no other language has an analogous feature. C's `switch` statement does not allow referring to C `const`s. + +# Unresolved questions + +[unresolved-questions]: #unresolved-questions + +The motivation for this RFC assumes that [trusted external statics](https://github.com/rust-lang/lang-team/issues/149) will eventually be implemented and stabilized. Other than that, there are no unresolved questions that I am aware of. + +# Future possibilities + +[future-possibilities]: #future-possibilities + +None; this is a very simple and self-contained feature. I've argued against some possible extensions in the [rationale and alternatives](#rationale-and-alternatives) section. Future changes to the structural equality rules might affect this feature, but that is anther discussion and out of scope for this RFC. From e46df7f5bbb1281c205f0b52b76f7616c54f0673 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 17 Aug 2022 13:31:02 +0200 Subject: [PATCH 02/18] Update PR number --- text/{0000-static-in-pattern.md => 3305-static-in-pattern.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename text/{0000-static-in-pattern.md => 3305-static-in-pattern.md} (99%) diff --git a/text/0000-static-in-pattern.md b/text/3305-static-in-pattern.md similarity index 99% rename from text/0000-static-in-pattern.md rename to text/3305-static-in-pattern.md index d39818029c8..49442e64971 100644 --- a/text/0000-static-in-pattern.md +++ b/text/3305-static-in-pattern.md @@ -1,6 +1,6 @@ - Feature Name: `static_in_pattern` - Start Date: 2022-08-17 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- RFC PR: [rust-lang/rfcs#3305](https://github.com/rust-lang/rfcs/pull/3305) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary From 2bfc0df3d5d180b01a2b1a8d2038f2e335a2a309 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 17 Aug 2022 13:56:16 +0200 Subject: [PATCH 03/18] Correct mistake in example --- text/3305-static-in-pattern.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/3305-static-in-pattern.md b/text/3305-static-in-pattern.md index 49442e64971..7e9f2afbd32 100644 --- a/text/3305-static-in-pattern.md +++ b/text/3305-static-in-pattern.md @@ -206,7 +206,9 @@ fn foo(scrutinee: stuff::PrivateZst) { // crate `stuff` #[derive(PartialEq, Eq)] #[non_exhaustive] -struct PrivateZst(); +pub struct PrivateZst(); + +pub static PRIVATE_ZST: PrivateZst = PrivateZst(); // main crate extern crate stuff; From 3858c98f5a46ef26fd8fcfd99e7bda3b49d85a2e Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 17 Aug 2022 12:07:35 -0400 Subject: [PATCH 04/18] Clarify "link-time" comment in motivation section --- text/3305-static-in-pattern.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3305-static-in-pattern.md b/text/3305-static-in-pattern.md index 7e9f2afbd32..a036244fd72 100644 --- a/text/3305-static-in-pattern.md +++ b/text/3305-static-in-pattern.md @@ -13,7 +13,7 @@ Allow referencing non-`mut` `static`s in pattern matches wherever referencing a [motivation]: #motivation -Rust pattern matches compare a scrutinee against compile-time information. Rust generally doesn't allow patterns to depend on runtime information; that is relegated to match guards. However, Rust currently also prevents patterns from depending on *link-time* information, specifically statics from `extern` blocks. +Rust pattern matches compare a scrutinee against compile-time information. Rust generally doesn't allow patterns to depend on runtime information; that is relegated to match guards. However, there is a category between "compile-time", when `rustc` runs, and "runtime", when Rust code runs. Some information a Rust program relies on may be determined at link-time, or by the target operating system, or before `main()` by the C runtime. Rust currently prevents patterns from depending on such information. Specifically, Rust patterns cannot reference statics from `extern` blocks. I encountered this restriction while trying to port the Rust standard library to [cosmopolitan libc](https://justine.lol/cosmopolitan/index.html). Cosmopolitan provides an API that mostly matches POSIX, with one major exception: constants like `ENOSYS` and `EINVAL`, which on most platforms are defined as C `#define`s (equivalent to Rust `const`s), are instead provided as C `const`s (equivalent to Rust non-`mut` `static`s). From 7f577d6b08b32c22937f917247dc56b776736f81 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Sun, 21 Aug 2022 22:06:44 +0200 Subject: [PATCH 05/18] Address nested and range patterns --- text/3305-static-in-pattern.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/text/3305-static-in-pattern.md b/text/3305-static-in-pattern.md index a036244fd72..2e7f0df14b0 100644 --- a/text/3305-static-in-pattern.md +++ b/text/3305-static-in-pattern.md @@ -225,6 +225,24 @@ fn foo(scrutinee: stuff::PrivateZst) { } ``` +Static patterns can be nested in other patterns: + +```rust +static ONE: i32 = 1; + +fn foo(scrutinee: i32) { + match scrutinee { + ONE | 2 => println!("a"), + _ => (), + } + + match (scrutinee, scrutinee) { + (ONE, ONE) => println!("a"), + _ => (), + } +} +``` + The examples above all use `match`, but statics would be allowed in all other language constructs that use patterns, including `let`, `if let`, and function parameters. However, as statics cannot be used in const contexts, static patterns are be unavailable there as well. # Drawbacks @@ -257,7 +275,8 @@ As far as I am aware, no other language has an analogous feature. C's `switch` s [unresolved-questions]: #unresolved-questions -The motivation for this RFC assumes that [trusted external statics](https://github.com/rust-lang/lang-team/issues/149) will eventually be implemented and stabilized. Other than that, there are no unresolved questions that I am aware of. + - The motivation for this RFC assumes that [trusted external statics](https://github.com/rust-lang/lang-team/issues/149) will eventually be implemented and stabilized. + - Should statics be accepted in range patterns (`LOW_STATIC..=HIGH_STATIC`)? One wrinkle is that the compiler currently checks at compile time that ranges are non-empty, but the values of statics aren't known at compile time. Such patterns could be either always accepted, accepted only when known to be non-empty (because the lower or upper bound is set to the minimum or maximum value of the type, respectively), or always rejected. # Future possibilities From 24b6ffb71bc464b6f6a5b69a91cd4a5c617b2bb3 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 24 Aug 2022 22:34:18 -0400 Subject: [PATCH 06/18] Address some of Ralf Jung's concerns --- text/3305-static-in-pattern.md | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/text/3305-static-in-pattern.md b/text/3305-static-in-pattern.md index 2e7f0df14b0..cd4f059a333 100644 --- a/text/3305-static-in-pattern.md +++ b/text/3305-static-in-pattern.md @@ -7,7 +7,7 @@ [summary]: #summary -Allow referencing non-`mut` `static`s in pattern matches wherever referencing a `const` of the same type would be allowed. +Allow referencing non-`mut` `static`s, including trusted statics from `extern` blocks, in pattern matches wherever referencing a `const` of the same type would be allowed. # Motivation @@ -142,7 +142,7 @@ For a static to be eligible for use in a pattern, it must: - not be marked `mut` - not be marked `#[thread_local]` - not come from an extern block, unless it is marked as safe to use with the [trusted external statics](https://github.com/rust-lang/lang-team/issues/149) feature -- have a type that satisfies the structural match rules, as described in [RFC 1445](1445-restrict-constants-in-patterns.md), but without any allowances for backward compatibility like there are for consts (e.g., floating point numbers in patterns) . These rules exclude all statics with interior mutability. +- have a type that satisfies the structural match rules, as described in [RFC 1445](1445-restrict-constants-in-patterns.md), but without any allowances for backward compatibility like there are for const matches (e.g., floating point numbers in patterns). These rules exclude all statics with interior mutability. In addition, function pointers and types that contain them are also excluded, as they do not implement `PartialEq`. Static patterns match exactly when a const pattern with a const of the same type and value would match. @@ -164,7 +164,7 @@ fn foo(scrutinee: bool) { } ``` -As an exception, when all safe values of a type are structurally equal, the compiler is allowed to see that the match will always succeed. +As an exception, when all valid values of a type are structurally equal, the compiler is allowed to see that the match will always succeed. ```rust // Not all `&()` are bitwise equal, @@ -243,8 +243,26 @@ fn foo(scrutinee: i32) { } ``` +When multiple identical static patterns appear in succession, the latter patterns are considered unreachable. +_(See [unresolved questions](#unresolved-questions) for major wrinkle)_ + +```rust +static ONE: i32 = 1; + +fn foo(scrutinee: i32) { + match scrutinee { + ONE => println!("a"), + // The following pattern is considered unreachable by the compiler + ONE => unreachable!(), + _ => (), + } +} +``` + The examples above all use `match`, but statics would be allowed in all other language constructs that use patterns, including `let`, `if let`, and function parameters. However, as statics cannot be used in const contexts, static patterns are be unavailable there as well. +Static patterns perform a runtime equality check each time the match arm/pattern is reached. In match statements, the value of the static is not cached between match arms, it is loaded anew from the static each time the static pattern is encountered. + # Drawbacks [drawbacks]: #drawbacks @@ -255,7 +273,9 @@ This change slightly weakens the rule that patterns can only rely on compile-tim [rationale-and-alternatives]: #rationale-and-alternatives -The proposed rules around reachability and exhaustiveness checking are designed to ensure that changing the value of a static, or changing from a static defined in Rust to a trusted extern static, is never a breaking change. The special dispensations for types with a single value could be considered unnecessary, as matching on such a type is a pointless operation. However, the rules are not difficult to implement (I managed to do it myself, despite near-zero experience contributing to the compiler), and are arguably the most correct and least surprising semantics. +The proposed rules around reachability and exhaustiveness checking are designed to ensure that changing the value of a static, or changing from a static defined in Rust to a trusted extern static, is never a breaking change. _This RFC assumes that the trusted externs feature will allow for Rust-defined and extern statics to be fully interchangeable_. + +The special dispensations for types with a single value could be considered unnecessary, as matching on such a type is a pointless operation. However, the rules are not difficult to implement (I managed to do it myself, despite near-zero experience contributing to the compiler), and are arguably the most correct and least surprising semantics. Allowing unsafe-to-access statics in patterns (`static mut`s, untrusted `extern` statics, `#[thread_local]` statics) is another possibility. However, I believe this option to be unwise: @@ -275,8 +295,9 @@ As far as I am aware, no other language has an analogous feature. C's `switch` s [unresolved-questions]: #unresolved-questions - - The motivation for this RFC assumes that [trusted external statics](https://github.com/rust-lang/lang-team/issues/149) will eventually be implemented and stabilized. - - Should statics be accepted in range patterns (`LOW_STATIC..=HIGH_STATIC`)? One wrinkle is that the compiler currently checks at compile time that ranges are non-empty, but the values of statics aren't known at compile time. Such patterns could be either always accepted, accepted only when known to be non-empty (because the lower or upper bound is set to the minimum or maximum value of the type, respectively), or always rejected. +- The motivation for this RFC assumes that [trusted external statics](https://github.com/rust-lang/lang-team/issues/149) will eventually be implemented and stabilized. +- Should statics be accepted in range patterns (`LOW_STATIC..=HIGH_STATIC`)? One wrinkle is that the compiler currently checks at compile time that ranges are non-empty, but the values of statics aren't known at compile time. Such patterns could be either always accepted, accepted only when known to be non-empty (because the lower or upper bound is set to the minimum or maximum value of the type, respectively), or always rejected. +- The current Stacked Borrows model allows mutating the target of an indirect shared reference in some cases; so a static of type `&&i32`, for example, could have its `i32` value change even in the middle of the pattern match. We could either disallow such statics in pattern matches, weaken reachability checking for them, or fully specify how exactly these matches can lead to UB (@RalfJung thinks the last option is impractical). # Future possibilities From d4cd41ca764d8b10d246b252c8f0a2e6ab4996df Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 25 Aug 2022 10:31:13 -0400 Subject: [PATCH 07/18] Address more of Ralf's concerns --- text/3305-static-in-pattern.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/text/3305-static-in-pattern.md b/text/3305-static-in-pattern.md index cd4f059a333..cb2b0e42010 100644 --- a/text/3305-static-in-pattern.md +++ b/text/3305-static-in-pattern.md @@ -15,7 +15,7 @@ Allow referencing non-`mut` `static`s, including trusted statics from `extern` b Rust pattern matches compare a scrutinee against compile-time information. Rust generally doesn't allow patterns to depend on runtime information; that is relegated to match guards. However, there is a category between "compile-time", when `rustc` runs, and "runtime", when Rust code runs. Some information a Rust program relies on may be determined at link-time, or by the target operating system, or before `main()` by the C runtime. Rust currently prevents patterns from depending on such information. Specifically, Rust patterns cannot reference statics from `extern` blocks. -I encountered this restriction while trying to port the Rust standard library to [cosmopolitan libc](https://justine.lol/cosmopolitan/index.html). Cosmopolitan provides an API that mostly matches POSIX, with one major exception: constants like `ENOSYS` and `EINVAL`, which on most platforms are defined as C `#define`s (equivalent to Rust `const`s), are instead provided as C `const`s (equivalent to Rust non-`mut` `static`s). +I encountered this restriction while trying to port the Rust standard library to [cosmopolitan libc](https://justine.lol/cosmopolitan/index.html). Cosmopolitan provides an API that mostly matches POSIX, with one major exception: constants like `ENOSYS` and `EINVAL`, which the POSIX standard specifies as C `#define`s (equivalent to Rust `const`s), are instead out of necessity provided as C `const`s (equivalent to Rust non-`mut` `static`s). ```rust // libc crate @@ -163,8 +163,7 @@ fn foo(scrutinee: bool) { } } ``` - -As an exception, when all valid values of a type are structurally equal, the compiler is allowed to see that the match will always succeed. +As an exception, when all valid values of a type are structurally equal, the compiler considers that the match will always succeed. ```rust // Not all `&()` are bitwise equal, @@ -208,14 +207,14 @@ fn foo(scrutinee: stuff::PrivateZst) { #[non_exhaustive] pub struct PrivateZst(); -pub static PRIVATE_ZST: PrivateZst = PrivateZst(); +pub static NON_EXHAUSTIVE_ZST: PrivateZst = PrivateZst(); // main crate extern crate stuff; fn foo(scrutinee: stuff::PrivateZst) { match scrutinee { - stuff::PRIVATE_ZST => println!("secrets abound"), + stuff::NON_EXHAUSTIVE_ZST => println!("secrets abound"), // `stuff::PrivateZst` is marked `#[non_exhaustive]` // and comes from an external crate, // so we can't tell that all values are equivalent. @@ -283,6 +282,8 @@ Allowing unsafe-to-access statics in patterns (`static mut`s, untrusted `extern` - It's not clear where the `unsafe` keyword would go (within the pattern? around the whole `match` or `let`? what about patterns in function parameters?) - it requires Rust to commit to and document, and users to understand, when exactly it is allowed to dereference the static when performing a pattern match +Another alternative is to add a new kind of pattern for runtime equality comparisons, with its own dedicated syntax. In addition to making the language grammar more complex, this option would prevent consts from being interchangeable with statics in pattern matches. + As for not making this change at all, I believe this would be a loss for the language as it would lock out the use-cases described above. This is a very simple feature, it doesn't conflict with any other potential extensions, the behavior and syntax fit well with the rest of the language, and it is immediately understandable to anyone who is already familiar with matching on `const`s. # Prior art From 7a6b6dadf1b4707acbfc14b9e76daeb2e997ce55 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 25 Aug 2022 11:21:42 -0400 Subject: [PATCH 08/18] Add D language switch statement to prior art --- text/3305-static-in-pattern.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3305-static-in-pattern.md b/text/3305-static-in-pattern.md index cb2b0e42010..ca1f42874bd 100644 --- a/text/3305-static-in-pattern.md +++ b/text/3305-static-in-pattern.md @@ -290,7 +290,7 @@ As for not making this change at all, I believe this would be a loss for the lan [prior-art]: #prior-art -As far as I am aware, no other language has an analogous feature. C's `switch` statement does not allow referring to C `const`s. +The D language's switch statement [allows referencing "runtime initialized const or immutable variable[s]"](https://dlang.org/spec/statement.html#switch-statement). However, C's `switch` statement does not allow referring to C `const`s. # Unresolved questions From 672b1260f041ba53747eda0f3db1fb6405c44027 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 25 Aug 2022 11:36:42 -0400 Subject: [PATCH 09/18] Add code example of Dlang feature in prior art --- text/3305-static-in-pattern.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/text/3305-static-in-pattern.md b/text/3305-static-in-pattern.md index ca1f42874bd..f32d976d789 100644 --- a/text/3305-static-in-pattern.md +++ b/text/3305-static-in-pattern.md @@ -290,7 +290,27 @@ As for not making this change at all, I believe this would be a loss for the lan [prior-art]: #prior-art -The D language's switch statement [allows referencing "runtime initialized const or immutable variable[s]"](https://dlang.org/spec/statement.html#switch-statement). However, C's `switch` statement does not allow referring to C `const`s. +The D language's switch statement [allows referencing "runtime initialized const or immutable variable[s]"](https://dlang.org/spec/statement.html#switch-statement), which are equivalent to non-`mut` `static`s in Rust, or `const`s in C. + +```d +import std; + +immutable(int) my_amazing_static = 42; + +void main() +{ + switch (42) + { + case my_amazing_static: + writeln("Match succeeded"); + break; + default: + writeln("Match failed"); + } +} +``` + +However, C's `switch` statement does not allow referring to C `const`s. # Unresolved questions From 9d7130ea9b826431305b63ad7f7816aa5016d4b7 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 25 Aug 2022 12:39:33 -0400 Subject: [PATCH 10/18] More about D's feature --- text/3305-static-in-pattern.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3305-static-in-pattern.md b/text/3305-static-in-pattern.md index f32d976d789..b6523062215 100644 --- a/text/3305-static-in-pattern.md +++ b/text/3305-static-in-pattern.md @@ -290,7 +290,7 @@ As for not making this change at all, I believe this would be a loss for the lan [prior-art]: #prior-art -The D language's switch statement [allows referencing "runtime initialized const or immutable variable[s]"](https://dlang.org/spec/statement.html#switch-statement), which are equivalent to non-`mut` `static`s in Rust, or `const`s in C. +The D language's switch statement [allows referencing "runtime initialized const or immutable variable[s]"](https://dlang.org/spec/statement.html#switch-statement), which are equivalent to non-`mut` `static`s in Rust, or `const`s in C. However, D also requires that these immutable variables be "of integral type"; they can't contain pointers/references. ```d import std; From e076ae12ce3ca34fc7fbb73246d83cf65a5ffea0 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 25 Aug 2022 12:40:48 -0400 Subject: [PATCH 11/18] Explain why macros aren't enough --- text/3305-static-in-pattern.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/3305-static-in-pattern.md b/text/3305-static-in-pattern.md index b6523062215..de5b4587058 100644 --- a/text/3305-static-in-pattern.md +++ b/text/3305-static-in-pattern.md @@ -286,6 +286,8 @@ Another alternative is to add a new kind of pattern for runtime equality compari As for not making this change at all, I believe this would be a loss for the language as it would lock out the use-cases described above. This is a very simple feature, it doesn't conflict with any other potential extensions, the behavior and syntax fit well with the rest of the language, and it is immediately understandable to anyone who is already familiar with matching on `const`s. +This feature cannot be fully emulated with a macro, because it's impossible to distinguish a static pattern from a wildcard binding without knowing what statics are in scope. And even an imperfect emulation would likely require proc macros, which can't easily be used inside the standard library. + # Prior art [prior-art]: #prior-art From 9f60fcbdd2e08ea7c3174b8929e59c4310d2e3de Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Sat, 27 Aug 2022 16:40:22 -0400 Subject: [PATCH 12/18] Resolve the nested references situation --- text/3305-static-in-pattern.md | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/text/3305-static-in-pattern.md b/text/3305-static-in-pattern.md index de5b4587058..4b3d0a5c2f7 100644 --- a/text/3305-static-in-pattern.md +++ b/text/3305-static-in-pattern.md @@ -163,6 +163,7 @@ fn foo(scrutinee: bool) { } } ``` + As an exception, when all valid values of a type are structurally equal, the compiler considers that the match will always succeed. ```rust @@ -242,17 +243,24 @@ fn foo(scrutinee: i32) { } ``` -When multiple identical static patterns appear in succession, the latter patterns are considered unreachable. -_(See [unresolved questions](#unresolved-questions) for major wrinkle)_ +When multiple identical static patterns appear in succession, the latter patterns are considered unreachable, unless the static's type contains nested references. This exception exists because under Stacked Borrows rules, a value behind multiple layers of immutable references might not actually be immutable. ```rust -static ONE: i32 = 1; +static AND_ONE: &i32 = &1; +static AND_AND_ONE: &&i32 = &&1; fn foo(scrutinee: i32) { - match scrutinee { - ONE => println!("a"), + match &scrutinee { + AND_ONE => println!("a"), // The following pattern is considered unreachable by the compiler - ONE => unreachable!(), + AND_ONE => unreachable!(), + _ => (), + }; + + match &&scrutinee { + AND_AND_ONE => println!("a"), + // The following pattern is considered reachable by the compiler + AND_AND_ONE => println!("b"), _ => (), } } @@ -266,7 +274,9 @@ Static patterns perform a runtime equality check each time the match arm/pattern [drawbacks]: #drawbacks -This change slightly weakens the rule that patterns can only rely on compile-time information. In addition, static patterns may have slightly worse performance than the equivalent constant patterns. +- This feature slightly weakens the rule that patterns can only rely on compile-time information. +- Static patterns may have slightly worse performance than the equivalent constant patterns. +- The rules around single-valued types and nested references add some additional complexity. However, based on the fully-functional implementation of this feature, I believe this complexity is not excessive. # Rationale and alternatives @@ -282,7 +292,9 @@ Allowing unsafe-to-access statics in patterns (`static mut`s, untrusted `extern` - It's not clear where the `unsafe` keyword would go (within the pattern? around the whole `match` or `let`? what about patterns in function parameters?) - it requires Rust to commit to and document, and users to understand, when exactly it is allowed to dereference the static when performing a pattern match -Another alternative is to add a new kind of pattern for runtime equality comparisons, with its own dedicated syntax. In addition to making the language grammar more complex, this option would prevent consts from being interchangeable with statics in pattern matches. +The special rules around reachability for static patterns containing nested references are necessary for soundness. One alternative is to forbid using such types in static patterns completely. However, this alternative expands the set of situations in which adding a reference to a type is a breaking change. + +Another alternative is to add a new kind of pattern for runtime equality comparisons, with its own dedicated syntax. In addition to making the language grammar more complex, this option would prevent existing const patterns from being interchangeable with static patterns. As for not making this change at all, I believe this would be a loss for the language as it would lock out the use-cases described above. This is a very simple feature, it doesn't conflict with any other potential extensions, the behavior and syntax fit well with the rest of the language, and it is immediately understandable to anyone who is already familiar with matching on `const`s. @@ -320,7 +332,6 @@ However, C's `switch` statement does not allow referring to C `const`s. - The motivation for this RFC assumes that [trusted external statics](https://github.com/rust-lang/lang-team/issues/149) will eventually be implemented and stabilized. - Should statics be accepted in range patterns (`LOW_STATIC..=HIGH_STATIC`)? One wrinkle is that the compiler currently checks at compile time that ranges are non-empty, but the values of statics aren't known at compile time. Such patterns could be either always accepted, accepted only when known to be non-empty (because the lower or upper bound is set to the minimum or maximum value of the type, respectively), or always rejected. -- The current Stacked Borrows model allows mutating the target of an indirect shared reference in some cases; so a static of type `&&i32`, for example, could have its `i32` value change even in the middle of the pattern match. We could either disallow such statics in pattern matches, weaken reachability checking for them, or fully specify how exactly these matches can lead to UB (@RalfJung thinks the last option is impractical). # Future possibilities From 7e9b3f9afb92d402821106fba339dbe91245195c Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Sun, 28 Aug 2022 00:53:10 -0400 Subject: [PATCH 13/18] Loosen statics with references reachability check --- text/3305-static-in-pattern.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/text/3305-static-in-pattern.md b/text/3305-static-in-pattern.md index 4b3d0a5c2f7..f0b0d4ad0f4 100644 --- a/text/3305-static-in-pattern.md +++ b/text/3305-static-in-pattern.md @@ -243,24 +243,24 @@ fn foo(scrutinee: i32) { } ``` -When multiple identical static patterns appear in succession, the latter patterns are considered unreachable, unless the static's type contains nested references. This exception exists because under Stacked Borrows rules, a value behind multiple layers of immutable references might not actually be immutable. +When multiple identical static patterns appear in succession, the latter patterns are considered unreachable, unless the static's type contains a reference. This exception exists because under Stacked Borrows rules, a value behind multiple layers of immutable references, and possibly even a single layer, might not actually be immutable. ```rust +static ONE: i32 = 1; static AND_ONE: &i32 = &1; -static AND_AND_ONE: &&i32 = &&1; fn foo(scrutinee: i32) { - match &scrutinee { - AND_ONE => println!("a"), + match scrutinee { + ONE => println!("a"), // The following pattern is considered unreachable by the compiler - AND_ONE => unreachable!(), + ONE => unreachable!(), _ => (), }; - match &&scrutinee { - AND_AND_ONE => println!("a"), + match &scrutinee { + AND_ONE => println!("a"), // The following pattern is considered reachable by the compiler - AND_AND_ONE => println!("b"), + AND_ONE => println!("b"), _ => (), } } @@ -292,7 +292,7 @@ Allowing unsafe-to-access statics in patterns (`static mut`s, untrusted `extern` - It's not clear where the `unsafe` keyword would go (within the pattern? around the whole `match` or `let`? what about patterns in function parameters?) - it requires Rust to commit to and document, and users to understand, when exactly it is allowed to dereference the static when performing a pattern match -The special rules around reachability for static patterns containing nested references are necessary for soundness. One alternative is to forbid using such types in static patterns completely. However, this alternative expands the set of situations in which adding a reference to a type is a breaking change. +The special rules around reachability for static patterns containing references are necessary for soundness. One alternative is to forbid using such types in static patterns completely. However, this alternative expands the set of situations in which adding a reference to a type is a breaking change. Another alternative is to add a new kind of pattern for runtime equality comparisons, with its own dedicated syntax. In addition to making the language grammar more complex, this option would prevent existing const patterns from being interchangeable with static patterns. From 1dc1ad447c67019e2b624d460ee55893aff19759 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Sun, 28 Aug 2022 01:03:25 -0400 Subject: [PATCH 14/18] Add example of static changing value --- text/3305-static-in-pattern.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/text/3305-static-in-pattern.md b/text/3305-static-in-pattern.md index f0b0d4ad0f4..1fa288ddd7f 100644 --- a/text/3305-static-in-pattern.md +++ b/text/3305-static-in-pattern.md @@ -266,6 +266,27 @@ fn foo(scrutinee: i32) { } ``` +The following code runs without panicking or undefined behavior. + +```rust +static mut MUT_INNER: i32 = 5; +static NESTED_REF: &&i32 = unsafe { &&MUT_INNER }; + +let val = match &&0 { + NESTED_REF => false, + _ if unsafe { + // Following is sound under Stacked Borrows rules, + // shared reference asserts immutability one level deep + **(core::mem::transmute::<_, *mut *mut i32>(NESTED_REF)) = 0; + false + } => false, + NESTED_REF => true, + _ => false, +}; + +assert!(val, true); +``` + The examples above all use `match`, but statics would be allowed in all other language constructs that use patterns, including `let`, `if let`, and function parameters. However, as statics cannot be used in const contexts, static patterns are be unavailable there as well. Static patterns perform a runtime equality check each time the match arm/pattern is reached. In match statements, the value of the static is not cached between match arms, it is loaded anew from the static each time the static pattern is encountered. From efa85791c082f54a53754072624e1d046008ca13 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Mon, 29 Aug 2022 20:19:20 -0400 Subject: [PATCH 15/18] Add note about macros --- text/3305-static-in-pattern.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/text/3305-static-in-pattern.md b/text/3305-static-in-pattern.md index 1fa288ddd7f..880cf9c643f 100644 --- a/text/3305-static-in-pattern.md +++ b/text/3305-static-in-pattern.md @@ -266,7 +266,7 @@ fn foo(scrutinee: i32) { } ``` -The following code runs without panicking or undefined behavior. +The following code runs without panicking or undefined behavior under the current Stacked Borrows rules. ```rust static mut MUT_INNER: i32 = 5; @@ -319,7 +319,8 @@ Another alternative is to add a new kind of pattern for runtime equality compari As for not making this change at all, I believe this would be a loss for the language as it would lock out the use-cases described above. This is a very simple feature, it doesn't conflict with any other potential extensions, the behavior and syntax fit well with the rest of the language, and it is immediately understandable to anyone who is already familiar with matching on `const`s. -This feature cannot be fully emulated with a macro, because it's impossible to distinguish a static pattern from a wildcard binding without knowing what statics are in scope. And even an imperfect emulation would likely require proc macros, which can't easily be used inside the standard library. +This feature cannot be fully emulated with a macro, because it's impossible to distinguish a static pattern from a wildcard binding without knowing what statics are in scope. And even an imperfect emulation would potentially require proc macros, which can't easily be used inside the standard library. Also, every crate that needs this feature would need to copy-paste the macro +implementation into their own source code. # Prior art From 4b9417f481a330bfb8d3c2548ea9e1f8c0929336 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Tue, 30 Aug 2022 23:40:50 -0400 Subject: [PATCH 16/18] Expand prior art section --- text/3305-static-in-pattern.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/text/3305-static-in-pattern.md b/text/3305-static-in-pattern.md index 880cf9c643f..15d4601143e 100644 --- a/text/3305-static-in-pattern.md +++ b/text/3305-static-in-pattern.md @@ -346,7 +346,11 @@ void main() } ``` -However, C's `switch` statement does not allow referring to C `const`s. +C's `switch` statement does not allow referring to C `const`s. + +C++'s `switch` statement allows arbitrary expressions. + +Scala allows matching on arbitrary expressions via special syntax (backticks). # Unresolved questions From 81232f7f049e873193e2ad2b872e9ed7e94d6d6a Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 31 Aug 2022 09:19:39 -0400 Subject: [PATCH 17/18] Correct mistake in prior art section --- text/3305-static-in-pattern.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3305-static-in-pattern.md b/text/3305-static-in-pattern.md index 15d4601143e..8d47a92aa16 100644 --- a/text/3305-static-in-pattern.md +++ b/text/3305-static-in-pattern.md @@ -348,7 +348,7 @@ void main() C's `switch` statement does not allow referring to C `const`s. -C++'s `switch` statement allows arbitrary expressions. +C++'s `switch` statement allows arbitrary constant expressions, but does not allow referring to C++ `extern const`s (only C++-defined `const`s). Scala allows matching on arbitrary expressions via special syntax (backticks). From 1d564f32d67bb23cdb7f433c37c5af7d99324116 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 31 Aug 2022 20:13:19 -0400 Subject: [PATCH 18/18] Address third-party crates and implementation complexity issues --- text/3305-static-in-pattern.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/text/3305-static-in-pattern.md b/text/3305-static-in-pattern.md index 8d47a92aa16..85ce81a9eb9 100644 --- a/text/3305-static-in-pattern.md +++ b/text/3305-static-in-pattern.md @@ -50,7 +50,7 @@ fn process_error(error_code: i32) { } ``` -Because Rust patterns don't support statics, all the `match` expressions in the standard library that refer to POSIX constants would currently need to be rewritten to accommodate Cosmopolitan. +Because Rust patterns don't support statics, all the `match` expressions in the standard library, and in third-party crates as well, that refer to POSIX constants would currently need to be rewritten to accommodate Cosmopolitan. ```rust // stdlib code adapted for cosmopolitan @@ -298,6 +298,7 @@ Static patterns perform a runtime equality check each time the match arm/pattern - This feature slightly weakens the rule that patterns can only rely on compile-time information. - Static patterns may have slightly worse performance than the equivalent constant patterns. - The rules around single-valued types and nested references add some additional complexity. However, based on the fully-functional implementation of this feature, I believe this complexity is not excessive. +- This feature may make complicate planned changes to the pattern-matching code. It may be best to postpone consideration of it until those changes are fully implemented, so we can see if the implementation is still simple with the new code. # Rationale and alternatives