Allow non-`mut` statics in patterns

Currently, the below code fails with E0530:

static ZERO: i32 = 0;

fn whee(x: i32) {
    match x {
        ZERO => println!("x is zero"),
        _ => println!("x is not zero"),
    }
}

Are there any reasons not to accept this code, with the ZERO pattern behaving as if ZERO was a const? Note that UnsafeCell does not implement StructuralEq, so interior mutable statics could not be used this way.


Motivation

I'm trying to build the Rust standard library for cosmopolitan libc. Cosmopolitan provides an interface that mostly matches POSIX, with one exception: many constants like ENOSYS are defined instead as statics that are initialized once before main to the proper platform-specific value. The Rust stdlib matches on these constants in many places; rewriting all those match statements would be a large maintenance burden.

Given this motivation, you are effectively needing match to be able to equality match its input against a value only known at runtime. (the static). This would have very different codegen from the status quo, and implications on things like match exhaustiveness or branch reachability. It feels strange to think that rust might add support for this, but only for statics and not for e.g. locals. (the only possible justification I can think of is that one could say that it is "path expressions" which are allowed to be used in a match)

3 Likes

When the static is defined by Rust, the compiler does know its value at compile-time; there should be difference between this csse and const in terms of exhaustiveness or reachability checking in patterns. When the static is declared inside an extern block, as in my motivating example, the Rust compiler doesn't know its value at compile time, and therefore can't use it to inform reachability or exhaustiveness checks. However, by the time any Rust code is running at runtime, its value will already have been fixed. If you define "constant" as "value won't change at runtime", an extern-block static is conceptually a constant value as well.

(Also, allowing patterns to refer to the values of locals would be a breaking change. This AFAIK is not)

I may be missing something, but it looks to me like ENOSYS is defined as a const in cosmopolitan to me

extern const in C is equivalent to extern "C" { pub static ... } (not static mut) in Rust. A Rust const would be like a C #define

5 Likes

I think this can be sidestepped very easilly right now like this:

 match x {
        val => if val == ZERO => println!("x is zero"),
        _ => println!("x is not zero"),
    }
}

It may not be very satisfying, but as it was said before, pattern matching and equality aren't quite the same thing.

2 Likes

This solution makes me sad because it would mean rewriting much of the standard library to accommodate my niche targetβ€”and I doubt the Rust libs team would want something like that merged upstream. Maybe my use case is just too niche; but on the other hand, if the implementation is not too complex, I see no reason not to allow this feature.

Patterns are compile time information matched against a runtime value. Making patterns depend on runtime information (even information that is guaranteed not to change once main is called) is a significant change to how pattern matching works. I think you'd probably need an actual RFC to hash out all the details of how that would actually work.

That being said, I totally get why you don't want to change all of those match blocks in the standard library. It may be worth asking the libs team for their input on solutions that are likeliest to get upstreamed.

2 Likes

For sure. I'm looking for obvious reasons not to have this feature, before I commit to the effort of hashing out details.

In any case, this is not the only new Rust feature needed for my use case; trusted extern statics would likely also be necessary.

If proc-macros are available to std, just use a cfg_attr(xxx_platform, rewrite_macro) on it, and rewrite the code pattern to the usable one inside the macro, will do all the magic, right?

I might be wrong, but it seems the constants are actually constant within single platform. Is it true?

If it's guaranteed you can make cosmopolitan target per platform with hardcoded platform specific constants.

The whole point of cosmopolitan is that a single executable works on any platform supported by it without requiring recompilation AFAIK.

3 Likes

An alternative approach could be to have std import the constants as constants (for example, Linux's constants), and have the errno or cvt functions convert between the platform values and the Linux values. This of course won't fix any user code which tries to match against ENOSYS for example, but neither would rewriting the matches. I don't know how this would work for non-errno constants.

std and rustc can't use standard procedural macros because of bootstrapping issues. Instead, they use directly implemented proc-macro-likes which are written against the compiler-internal types rather than the proc_macro bridge types.

Update: I've implemented this feature at GitHub - Jules-Bertholet/rust at statics_in_patterns

Update: RFC posted

1 Like