Stream: t-lang

Topic: "Why use `const` over `let`?"


Lokathor (Apr 29 2020 at 23:55, on Zulip):

A const is absolutely required to have been evaluated at compile time. A let might or might not be.

I once asked on github "can we get some way to have an assurance that let with a const fn and const args will be compile time evaluated?" and the answer (i forget who from) was "No, just use a const if you want that".

So if you want to ensure compile time evaluation, you need to use const

Sebastian Malton (Apr 30 2020 at 13:23, on Zulip):

Why not use both in tandem?

const X: T = E;
let Y = X;
Charles Lew (Apr 30 2020 at 13:48, on Zulip):

I think const generics will eventually provide something here. For example:

struct ConstVal<T, const V: T>(PhantomData<T>);
impl<T, const V:T> ConstVal<T, {V}> {
       const VALUE: T = V;
}

Then you can just ConstVal<i32, {E}>::VALUE to get a const value of E.

Hanna Kruppe (Apr 30 2020 at 14:40, on Zulip):

There's occasional talk of a const { <expr> } syntax, e.g. as an alternative to expanding the scope of promotion. If introduced, that would be both more convenient to use and easier to fully implement and stabilize than using const generics.

Lokathor (Apr 30 2020 at 17:33, on Zulip):

(Note that the topic was written in the context of the edition meeting earlier this week that was posted on youtube where this was pondered by several of the lang team.)

Sebastian, if you have a const you often don't need to also bind as a let. You can just pass the const to a function, or whatever it was you were going to do.

Personally, I don't think it's a big deal, i've come to terms with just writing consts into function bodies all the time when i need to ensure that they're consts. It's like 1% annoying to learn about and then you get over it.

scottmcm (Apr 30 2020 at 17:36, on Zulip):

I have (very-occasionally) wanted to be able to make an arm that's 0..const{N-1} where having to make a new const item is a pain, especially if N is based on a generic argument somehow

scottmcm (Apr 30 2020 at 17:40, on Zulip):

Hmm, I think you can do a horrible hack to force a let to be compile-time, or error if not:

macro_rules! must_be_const {
    ($e:expr) => {
        { let x: &'static _ = &$e; *x }
    }
}

fn main() {
    must_be_const!(1+1);
    // must_be_const!(4_i32.leading_zeros()); // FAILS
    must_be_const!(std::mem::size_of::<i32>() * 4);
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=99083194dcf153af535d359ce823f47a

scottmcm (Apr 30 2020 at 17:41, on Zulip):

That's absolutely an abuse of RSP, though, and I'm kinda sad that it works...

Hanna Kruppe (Apr 30 2020 at 17:41, on Zulip):

This forces the expression to be promotable, which is a small subset of const. In particular it excludes all const fn calls except a few magic ones that were white listed for backwards compatibility

Lokathor (Apr 30 2020 at 17:42, on Zulip):

(what's a promotable const? i hear it a lot and i don't know what it means)

Hanna Kruppe (Apr 30 2020 at 17:51, on Zulip):

The only decent write up I know of is https://github.com/rust-lang/const-eval/blob/master/promotion.md

Lokathor (Apr 30 2020 at 17:54, on Zulip):

Oh, okay.

Also i'd say that the only pain point to having to write const x = expr compared to let x = expr is that you have to give a type to all consts. I don't know how crazy it would be to just not require an explicit type on private consts.

Sebastian Malton (Apr 30 2020 at 18:50, on Zulip):

Lokathor said:

Sebastian, if you have a const you often don't need to also bind as a let. You can just pass the const to a function, or whatever it was you were going to do.

Ah I didn't know it came from the lang meeting. I thought you wanted a global let, that was guaranteed to be evaluated at compile time.

scottmcm (Apr 30 2020 at 19:10, on Zulip):

I think that there's certainly a possibility of removing explicit type annotations on some set of private/inner/etc items. I've wanted it for random things like local _functions_ sometimes too. So my not-thoroughly-considered inclination here is that it shouldn't just be for consts, but should be a tweak to the general item rule or some opt-in to make things more structural or macro-like or something.

Like we could have ☃ const N = 100; or ☃ fn foo(a, b) { (a + b).sqrt() } that works more like a macro than generics or type inference.

(Using for obvious strawman syntax -- a real one could be something like macro, but I'll avoid that for now)

nikomatsakis (Apr 30 2020 at 20:02, on Zulip):

I favor const { expr } fwiw

nikomatsakis (Apr 30 2020 at 20:03, on Zulip):

let x = const { 100 }; seems ok?

nikomatsakis (Apr 30 2020 at 20:04, on Zulip):

anyway I didn't really read through this thread in full so I'll be quiet now :)

scottmcm (Apr 30 2020 at 20:45, on Zulip):

That seems like a nice way to simplify the promotable rules as well, if we wanted to in future -- &const { ... } would always be promotable.

RalfJ (Apr 30 2020 at 21:29, on Zulip):

so about once a month const { ... } comes up, and more and more people say they like the idea

RalfJ (Apr 30 2020 at 21:30, on Zulip):

anyone volunteering for writing an RFC? :D

RalfJ (Apr 30 2020 at 21:30, on Zulip):

(IIRC @oli once said they had that planned, not sure how far that got)

ecstatic-morse (Apr 30 2020 at 21:39, on Zulip):

I would be happy to do it if @oli is too busy.

scottmcm (Apr 30 2020 at 23:04, on Zulip):

I was thinking the same, @RalfJ :D

I'd be happy to participate, @ecstatic-morse. Apparently we're nearing 2 years since my post about it. (Not to say that was the first mention. I'm sure others thought of it earlier.)

ecstatic-morse (Apr 30 2020 at 23:11, on Zulip):

Ah! I was wondering who first proposed this. Though like you say, this has probably been invented independently several times

ecstatic-morse (Apr 30 2020 at 23:15, on Zulip):

I envision const { ... } as an expression that would function like { const PRIVATE: InferredType = { ... }; PRIVATE }. We should also extend the grammar to make it valid in patterns as you mention. I would need to look at the pattern grammar again to say more.

ecstatic-morse (Apr 30 2020 at 23:18, on Zulip):

I would allow const {} in all const contexts out of the box since we currently allow const declarations in them.

ecstatic-morse (Apr 30 2020 at 23:18, on Zulip):

(this is centril's question number 2)

ecstatic-morse (Apr 30 2020 at 23:23, on Zulip):

Otherwise this is pretty much what I had in mind.

ecstatic-morse (Apr 30 2020 at 23:25, on Zulip):

Oh, and I would call these anonymous consts or anonymous const expressions to emphasize that they have the same semantics as { const X = expr; X }

ecstatic-morse (Apr 30 2020 at 23:26, on Zulip):

Though I think "constant expression" might already be in use?

scottmcm (Apr 30 2020 at 23:26, on Zulip):

Or, equivalently, const fn private() -> Inferredtype { ... } private(), yeah.

Might be good enough just to say that the inside of said blocks is a "const context" following the same rules as other such places (like array lengths). Since I don't think we'd want to actually implement this as a desugar to an item.

While I want it in patterns, I might actually leave that for a different PR, since the expression form is a completely separable concern for the pattern form. And now that we have both const fn and async fn, but only async{} and not const{}, it feels like the const{} is a much more obviously-correct extension.

ecstatic-morse (Apr 30 2020 at 23:28, on Zulip):

In any case, we will need it in const fn, since those don't always run at compile time.

scottmcm (Apr 30 2020 at 23:28, on Zulip):

ecstatic-morse said:

I would also allow const {} in all const contexts out of the box since we currently allow const declarations in them.

Probably allowed syntactically, but warn-by-default-linted, so const { const { foo() } } is linted, same as unsafe { unsafe { foo() } }.

ecstatic-morse (Apr 30 2020 at 23:30, on Zulip):

The const fn desugaring isn't quite equivalent though, since it would make an anonymous const ineligible for promotion.

ecstatic-morse (Apr 30 2020 at 23:32, on Zulip):

That actually might be worth discussing. Do we want people to write &const { u32::max_value() } or const { &u32::max_value() } to get a &'static u32? I suspect we want the latter?

scottmcm (Apr 30 2020 at 23:32, on Zulip):

Oh, thank you past-self for putting a nice example use for the const pattern in that thread: match x { const{'z' as i32} => ...

ecstatic-morse (Apr 30 2020 at 23:34, on Zulip):

But it seems like part of the motivation for const {} is to make things eligible for promotion in contexts besides rvalue static promotion (e.g. array initalizers), so I think const {} should be eligible for promotion.

scottmcm (Apr 30 2020 at 23:35, on Zulip):

The latter would error under current rules, right?

scottmcm (Apr 30 2020 at 23:36, on Zulip):

I think I'd just say that const blocks (themselves) are always eligible for promotion, yeah, not that their bodies get any special rules different from the current ones in a const context

ecstatic-morse (Apr 30 2020 at 23:36, on Zulip):

No. const X: &u32 = &u32::max_value(); is legal today.

scottmcm (Apr 30 2020 at 23:40, on Zulip):

Is that because it's in the white-list, or is everything in const context always promotable?

scottmcm (Apr 30 2020 at 23:41, on Zulip):

Because aesthetically I don't like &const { } at all, since it looks way too much like &mut

ecstatic-morse (Apr 30 2020 at 23:41, on Zulip):

That actually doesn't rely on promotion at all. Everything at the "top-level" of a const initializer has the static lifetime.

scottmcm (Apr 30 2020 at 23:42, on Zulip):

Oh, of course. That makes sense, same as how all elided lifetimes in consts are 'static because of course they are

scottmcm (Apr 30 2020 at 23:43, on Zulip):

So great, sounds like const{} doesn't need to ever be promotable, since they can just "move the & inside".

Well, actually, what does &(1 + const{ i32::min_value() }) do? Is it promotable?

ecstatic-morse (Apr 30 2020 at 23:44, on Zulip):

It does. One of the motivations is to allow [const { Vec::new() }; 42].

scottmcm (Apr 30 2020 at 23:46, on Zulip):

So I guess the RFC _would_ add the "const{...} is always promotable" rule, but there'd be a non-normative note that clippy might want to lint against &const{...}?

ecstatic-morse (Apr 30 2020 at 23:46, on Zulip):

I think that's exactly right.

ecstatic-morse (Apr 30 2020 at 23:47, on Zulip):

Promotion appears to work for &(1+1), which is somewhat unexpected?

scottmcm (Apr 30 2020 at 23:48, on Zulip):

ecstatic-morse said:

[const { Vec::new() }; 42]

Talk me through what you're thinking between [const { Vec::new() }; 42] and const { [Vec::new(); 42] }, please. I'm not sure what rule you're invisioning in that post.

scottmcm (Apr 30 2020 at 23:48, on Zulip):

I don't have a clue what the rules for promotion are :sweat_smile:

ecstatic-morse (Apr 30 2020 at 23:49, on Zulip):

As an aside, the original rvalue static promotion RFC/implementation was overbroad IMO, we ended up promoting a lot of stuff that was not explicitly mentioned. wg-const-eval (especially RalfJ) has been trying to limit the use of promotion for this reason.

ecstatic-morse (Apr 30 2020 at 23:50, on Zulip):

Lemme find some links

scottmcm (Apr 30 2020 at 23:50, on Zulip):

I do think this would let us move towards a potentially simpler rvalue-static-promotion rule over an edition boundary. Like saying that only literals and const{}s are promoted.

ecstatic-morse (Apr 30 2020 at 23:50, on Zulip):

Promotability rules

scottmcm (Apr 30 2020 at 23:52, on Zulip):

And that the future-change lint could have a cargo-fix that changes complicated promotion situations to const{} blocks so the migration is easy

ecstatic-morse (Apr 30 2020 at 23:53, on Zulip):

Talk me through what you're thinking between [const { Vec::new() }; 42] and const { [Vec::new(); 42] }, please. I'm not sure what rule you're invisioning in that post.

Ah, I hadn't thought of putting the braces outside. Because the part to the right of the semicolon is already a const context, I believe these two are functionally equivalent? It's just a matter of style.

ecstatic-morse (Apr 30 2020 at 23:54, on Zulip):

That's because we promote function calls inside a constant. This is the relevant section.

scottmcm (Apr 30 2020 at 23:55, on Zulip):

Ah, the extra 2203 rules mentioned in your earlier link clear that up

ecstatic-morse (Apr 30 2020 at 23:55, on Zulip):

scottmcm said:

I do think this would let us move towards a potentially simpler rvalue-static-promotion rule over an edition boundary. Like saying that only literals and const{}s are promoted.

Very much in agreement with this BTW.

scottmcm (Apr 30 2020 at 23:57, on Zulip):

That's a great document. I thought I wanted to simplify things before, but having skimmed through it now I know i do :upside_down:

ecstatic-morse (Apr 30 2020 at 23:58, on Zulip):

Yeah, promotion is really hard to reason about. https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3d4b2a0825edc53ade0e2f0a91c56144

ecstatic-morse (May 01 2020 at 00:00, on Zulip):

That's actually called out specifically as okay by the promotion rules, although I think they were partly trying to document behavior that had accidentally been stabilized. As long as it doesn't happen in release mode it's fine.

ecstatic-morse (May 01 2020 at 00:01, on Zulip):

You can see I am rediscovering the edge cases as I go, and I wrote a not-insignificant portion of that document XD

scottmcm (May 01 2020 at 00:05, on Zulip):

Probably too scope-creepy to include in the RFC, but it came to mind so I figured I'd say it:

What about static blocks? That'd let it produce a place instead of a value.

scottmcm (May 01 2020 at 00:06, on Zulip):

Hmm, probably the linking monomorphization questions about that would be too scary to want to consider.

ecstatic-morse (May 01 2020 at 00:09, on Zulip):

I've never needed an anonymous compile-time value that is also guaranteed to reside in memory.

ecstatic-morse (May 01 2020 at 00:10, on Zulip):

Although it's technically possible I think? I'm just not terribly interested in speccing it out.

scottmcm (May 01 2020 at 00:11, on Zulip):
fn per_type_counter<T>()->usize{
    static{AtomicUsize::new()}.increment(enum::SeqCst)
}
scottmcm (May 01 2020 at 00:12, on Zulip):

But yeah, let's not bother.

scottmcm (May 01 2020 at 00:12, on Zulip):

Way too many scary corners for its minimal-at-best value

ecstatic-morse (May 01 2020 at 00:18, on Zulip):

Okay, so to summarize, what are things you feel are settled? These are mine:

ecstatic-morse (May 01 2020 at 00:21, on Zulip):
ecstatic-morse (May 01 2020 at 00:22, on Zulip):

Unsettled for me are:

Lokathor (May 01 2020 at 00:24, on Zulip):

I think the second form has to be the form.

Lokathor (May 01 2020 at 00:25, on Zulip):

You force func() in a const context and get a value, then duplicate that 42 times.

Lokathor (May 01 2020 at 00:26, on Zulip):

wait isn't an array init already a const context you said?

ecstatic-morse (May 01 2020 at 00:26, on Zulip):

array length is. Array initializers are a "promotion context"

ecstatic-morse (May 01 2020 at 00:26, on Zulip):

(on nightly with the feature flag that is)

Lokathor (May 01 2020 at 00:28, on Zulip):

ah, then it should be the inner form, yeah. Expressions should always make sense when evaluated innermost to outer (as much as possible), so you need to make it a const context to init the array

ecstatic-morse (May 01 2020 at 00:28, on Zulip):

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=144dcc98d6467d99e647dfe3810f9298

ecstatic-morse (May 01 2020 at 00:30, on Zulip):

@Lokathor both forms would be valid with the current promotion rules.

Lokathor (May 01 2020 at 00:30, on Zulip):

oh, hmm.

scottmcm (May 01 2020 at 00:38, on Zulip):

Lokathor said:

ah, then it should be the inner form, yeah. Expressions should always make sense when evaluated innermost to outer (as much as possible), so you need to make it a const context to init the array

I don't think that's necessarily true for effect contexts. If you replace const with unsafe they're both fine, and it's a style-not-language-definition question which to use

scottmcm (May 01 2020 at 00:43, on Zulip):

ecstatic-morse said:

async{} was called "async blocks" (https://github.com/rust-lang/rfcs/blob/master/text/2394-async_await.md#async-blocks), so maybe just "const blocks".

"anonymous consts" makes me think of that const _: usize = ...; feature.

ecstatic-morse (May 01 2020 at 00:45, on Zulip):

Those are called "unnamed" constants in the reference, but point taken.

ecstatic-morse (May 01 2020 at 00:49, on Zulip):

I'll just refer to it as const {} for now and we can have a final bikeshedding vote (either org members or just lang-team members is fine) at some point in the future.

oli (May 01 2020 at 08:55, on Zulip):

I don't remember if I said I was working on an RFC, but I was working on a prototype PR, which showed many many impl problems with it, which I've started to solve because the problems exist for array lengths, too

oli (May 01 2020 at 08:55, on Zulip):

but that shouldn't block an RFC

RalfJ (May 01 2020 at 09:49, on Zulip):

ecstatic-morse said:

As an aside, the original rvalue static promotion RFC/implementation was overbroad IMO, we ended up promoting a lot of stuff that was not explicitly mentioned. wg-const-eval (especially RalfJ) has been trying to limit the use of promotion for this reason.

another link: https://github.com/rust-lang/const-eval/issues/19

RalfJ (May 01 2020 at 09:52, on Zulip):

@scottmcm also see https://github.com/rust-lang/const-eval/blob/master/promotion.md#implicit-and-explicit-contexts for the difference in promotion rules (anonymous) consts and normal run-time code.

RalfJ (May 01 2020 at 09:53, on Zulip):

another open question for the RFC:
should we work towards not promoting any fn calls outside explicit promotion contexts? i.e., currently this works:

let x: &'static i32 = &i32::max_value();

should we require const { &i32::max_value() } instead?
(well in this specific case, &i32::MAX will also work without explicit const, but you get the idea)

RalfJ (May 01 2020 at 09:54, on Zulip):

that would basically amount to try to remove rustc_promotable from as many functions as we can get away with, and maybe deprecating the ability for functions where just removing it causes too much breakage

scottmcm (May 01 2020 at 17:37, on Zulip):

I do like the idea of eliminating the "these functions are special" list. I don't know if it'd be worth trying to roll them back or just saying that we'll never add any more because you can get it by a user-opt-in instead of the library-opt-in

ecstatic-morse (May 01 2020 at 17:37, on Zulip):

Ah, I didn't know max_value was #[rustc_promotable]

ecstatic-morse (May 01 2020 at 17:47, on Zulip):

re: removing #[rustc_promotable] when possible, I'm not super eager to break back compat, even with a crater run since coverage is not 100%. The following functions are currently marked
#[rustc_promotable].

ecstatic-morse (May 01 2020 at 17:51, on Zulip):

Except for the RawWaker ones, I don't think any of these could plausibly have observable side-effects (lemme know if you disagree). Perhaps making #[rustc_promotable] no longer work would be a good candidate for the next edition?

ecstatic-morse (May 01 2020 at 17:52, on Zulip):

With const {} as the suggested replacement.

scottmcm (May 01 2020 at 17:59, on Zulip):

While that list doesn't make me happy (..= but not .. seems weird, for example), it looks small enough to not be a pervasive problem. And a few of those I wish were just real consts anyway (like size_of), so behaving like them doesn't seem crazy.

Maybe instead we could just warn about non-literals being promoted and suggest adding a const{} to be clear about what's going on. That's a prerequisite to a potential edition change anyway, and we can defer any decision about actually blocking it over an edition boundary.

ecstatic-morse (May 01 2020 at 18:12, on Zulip):

Warning about all non-literals seems good to me (although I don't think it will be trivial to implement).

ecstatic-morse (May 01 2020 at 18:13, on Zulip):

Also, I believe .. is desugared to a struct literal (Range { .. }), so it will also get promoted. Presumably we added #[rustc_promotable] to RangeInclusive to make it behave the same.

RalfJ (May 02 2020 at 09:14, on Zulip):

of these, I'd say Duration::from_secs stands out.
also I assume it's min/max_value for all int types, not just i32?

RalfJ (May 02 2020 at 09:15, on Zulip):

I got away with de-promoting some Duration methods in https://github.com/rust-lang/rust/pull/67531. might be worth doing that for from_secs as well?

RalfJ (May 02 2020 at 09:15, on Zulip):

the RawWaker ones are actually fairly recent, but AFAIK they are also just constructor wrapper, which was a requirement for making them promotable

RalfJ (May 02 2020 at 10:20, on Zulip):

RalfJ said:

I got away with de-promoting some Duration methods in https://github.com/rust-lang/rust/pull/67531. might be worth doing that for from_secs as well?

I am proposing that in https://github.com/rust-lang/rust/pull/71796

ecstatic-morse (May 04 2020 at 18:53, on Zulip):

Opened https://github.com/rust-lang/rfcs/pull/2920

Last update: Jun 05 2020 at 22:25UTC