Allow 'const _'?


#1

Currently, it’s illegal to create a constant with the name _. I propose that we allow this, giving it the same semantics as let _ = ... - namely, that the result is discarded.

Constants are often used for the side effect of determining whether some code compiles. This is used in serde, for example, to determine whether certain properties hold about a type. Currently, serde has to do naming tricks like:

const _NAME_THATS_UNLIKELY_TO_ALREADY_BE_USED: () = { <expression that won't compile if certain properties don't hold>; };

This is both ugly (it produces ugly user-visible compiler output when it fails) and error prone, as it’s not guaranteed that the symbol will be unique. Instead, if _ were a supported constant identifier, this could be simplified to:

const _: () = { ...; };

Thoughts?

cc @dtolnay @erickt


Pre-RFC: Underscore functions
#2

What about the const_assert!/static_assert! macros that I’ve seen proposed elsewhere, would these cover all the cases where you would use const _ =?


#3

Unfortunately not; this isn’t about checking the result of a boolean expression, but about seeing whether certain code even type checks. As a concrete example, imagine something like:

const _: () = {
    struct IsFoo<F: Foo>(PhantomData);
    let _ = IsFoo::<Bar>(PhantomData); // doesn't compile if Bar: !Foo
    ()
};

#4

Actually, I just realized that this is necessary to even define const_assert! as well (at least as a non-builtin). A const is an item so is non-hygienic in macro_rules! macros (here’s a playground playing with a macro for this sort of assertion)


#5

This would be great! Do you want to open an RFC, or even go ahead and implement it to kickstart an official discussion?

I think the best place to put it would be the parser, we already have some clever handling for use foo as _; (it gets an unique identifier and nothing else needs to know about it):

so you can pretty much just copy that here: (if you do that it will work for all three const _, static _ and static mut _)

You’ll alone need this to feature-gate it:


#6

Something more involved could be a const { ... } block in which all statements and expressions must successfully evaluate at compile-time, e.g.:

fn precompute() -> &'static [u8; 16] {
    // Without `const` it wouldn't get rvalue-promoted.
    &const {
        let mut data = [u8; 16];
        // Do something potentially expensive.
        for i in 0..16 { data[i] += i; }
        // Compile-time assertion.
        assert_eq!(data[15], 15);
        data
    }
}

We could then also allow it at the module level, outside of functions, making static_assert!(x) be just const { assert!(x) }.


#7

Unfortunately I don’t have time to do an implementation, but here’s an RFC; let me know if that looks good or if you have any feedback, and then I’ll submit a PR.


#8

I don’t mind this idea.

However, in today’s Rust, _ is permitted in two different contexts with two different meanings:

  1. In type contexts it means “please infer the type”.
  2. In patterns _ means “I don’t care / match anything”.

If we permit const _, then that makes for another 3rd context, which is quite ad-hoc. If instead we could bring const closer to let bindings, then we could support const $pat and things such as:

const (FOO, BAR): (usize, usize) = (1, 2);

In that case, const _ falls out naturally because _ is a pattern.


#9

I’m happy with this approach too; in fact, I mention it as an alternative in the RFC. I just figured that it might be easier to implement only _ first before doing general-purpose pattern matching later. I don’t know much about compiler internals; maybe that’s not true.


#10

It’s much easier, I’ve already given the instructions above, it should take mere minutes.

That’s because it’s still technically a named const / static, not anything involving patterns, so the entire compiler wouldn’t even bat an eye, it’s just the parser that’d support it by “making a fake name” on the fly.

I’m not even sure where we’d start with patterns, we’d have to support (A: T, B: U) instead of (A, B): (T, U) and/or infer binding types from pattern types, which is sort of “type ascription in patterns” territory either way.


#11

I wouldn’t mind going for const _: $type = $expr; first as an intermediate step along the way, as long as the end result supports arbitrary patterns; But I sorta want to avoid stabilizing const _ and then leaving the language in an inconsistent state where we don’t have const $pat eventually.

Wrt. type ascription in patterns and inferring types from patterns; this is something I’ve introduced for functions in my type ascription RFC; so I’m hoping we can reuse some of that machinery here.


#12

OK, I’ve updated my RFC to explicitly suggest that this is a first step on the way to general patterns. Let me know if you think the RFC looks good or if there’s stuff to add, and then I’ll submit a PR.


#13

I’ve added some line comments :slight_smile:


#14

Thanks for all the great comments! I’ve pushed a new version that addresses them.


#15

The RFC has been submitted.