This is a tracking issue for the RFC "Support underscores as constant names" (rust-lang/rfcs#2526).
Steps:
static _
as well~Unresolved questions:
None
Ugh, seems like a legitimized hack rather than a... well, any of several proper features to which this can be generalized.
At least this is a very tiny hack.
Implementation strategy is the same as for use Trait as _;
.
If we encounter _
instead of the const name identifier, then we create a gensym for it instead of storing it in AST directly (so that different _
s do not conflict).
Together with a feature gate and tests that would be all the implementation.
Ugh, seems like a legitimized hack rather than a... well, any of several proper features to which this can be generalized.
Hehe, I agree we should generalize (to patterns) eventually but it seemed that it was much more complex from @eddyb's comments. :)
looks interesting, I will give it a try!
@petrochenkov
Can we also ensure that those gensyms get removed after before codegen? This means, the generated MIR should be removed in some MIR rounds.
We don't need to remove any MIR
. Unused private constants don't end up in metadata.
@oli-obk
Fine. There is one "inconsistency" in Rust today:
let _: str = *"123"; //Legal
let _v: str = *"123"; //Illegal unless unsized_rvalues
When implementing this, how do we want to justify
const _: str = *"123";
const _v: str = *"123";
Shall we repeat the inconsistency, or shall we count both valid or invalid?
I'd start out with both being disallowed, since that is the current behaviour of constants. But I have no strong opinion.
Setting earliest date for stabilization proposal to 25th november 2018 (14 days from now).
We'll likely want to discuss what direction generalizations should go in before stabilizing.
The RFC did not discuss static _: T = ...;
. Is that supposed to work, too? (the implementation does in fact allow this, accidentally even without a feature gate)
Fix for the stability hole is in https://github.com/rust-lang/rust/pull/55983
I'm having a bit of second thoughts about this RFC. I feel like (a) the intention of const _: T = { .. }
is pretty unclear and (b) it opens some weird questions about e.g. generic constants and how they fit here and (c) it doesn't support full patterns, which it kind of suggests it would support.
The motivation of "ensure some code type-checks" is real though. I sort of like the syntax const { ... }
for that purpose. Thoughts?
I guess I'm probably just re-raising thoughts that came up on the RFC.
I sort of like the syntax const { ... } for that purpose. Thoughts?
Could that be re-used for explicit promotion as @oli-obk plans it?
@RalfJung seems plausible. The one concern I could see is whether there would be some conflict as const { .. }
could be an item and an expression, and we might want the latter to inherit the lexical scope from the surrounding fn, so it can refer to generic constant names -- but we could just say that const { .. }
items cannot appear in functions (since...what's the point).
but we could just say that const { .. } items cannot appear in functions (since...what's the point).
Wouldn't that defeat the purpose of explicit promotion?
With respect to a) the intention of const _: T = { ... };
being unclear I agree with that in isolation. However, in the context of being wrapped in a macro such as const_assert! { ... }
or inside a derive macro it feels rather clear what the purpose of this construct is.
My main worry is instead b) and c). That is, would we permit this?
const (A, B): (AType, BType) = (1, 2);
(this would be the natural extension of const _: Type = expr;
because _
acts as a pattern in this context.
but also this?
const A<T>: AType<T> = expr;
and how can these be composed?
If we write:
const (A<T>, B<U>): (AType<U>, BType<T>) = expr;
does that make any sense?
I suppose that we could support generic constants when A<T>
is written and patterns otherwise; that likely works syntactically but it could cause confusion (or maybe not... I'm not sure).
Finally, ideally, to keep the language consistent, we should keep const
and static
items as much as possible in sync syntactically. If we support const _: Type = expr;
we should imo support static _: Type = expr;
even if we lint on the latter.
As for supporting const { ... }
, I think the meaning here isn't clear... there could be 2 different interpretations:
{ ... }
is evaluated at compile time. This interpretation is based on const
items.let x = random(); let y = const { x * x };
. This interpretation is based on const fn
items. 2. would subsume 1.Wouldn't that defeat the purpose of explicit promotion?
const
expressions would be allowed in function bodies, just const
items would not.
I feel like (a) the intention of const _: T = { .. } is pretty unclear
I believe it is pretty clear to user as we can use let VAR = { ... }
The entire block may be evaluated at compile time if all variables referenced are. Notably, you could write: let x = random(); let y = const { x * x };. This interpretation is based on const fn items. 2. would subsume 1.
I don't think that's a useful interpretation. There's absolutely nothing this const
block could promise us. It's just regular code.
I agree with everything else. We should just allow constants with patterns and generic constants. We already support them inconveniently by defining a const function without arguments.
It's just regular code.
It's not. Notably, you may only reference existing bindings. Any function calls must still be const fn
. For example, let y = const { random() };
would not be allowed.
I don't think that's a useful interpretation. There's absolutely nothing this
const
block could promise us.
There's plenty this would tell you:
Now.. it might be confusing to have block form entitled const { .. }
behave in this way, but that doesn't mean it isn't useful to behave in this way.
I don't really understand how generic constants can cause issues with unnamed consts, and AFAIK supporting _
is fully forwards-compatible with generalising const
to fully accept patterns. Is there any chance this could be stabilised? Many derive macros out there currently generate a difficult-to-guess const name to scope extern crate
and use
statements.
I would like to add my voice to getting this stabilized. I use it for two reasons: using static_assertions without having to put a dummy function name (see static_assertions limitations) and to include external files from proc macro (from internals talk, an example in pest - using a hard-to-guess name).
This feature as spec'd doesn't seem very controversial and forward-compatible with whatever extension to const patterns we might want in the future.
Hmm... I'm having second-second thoughts about this.
Provided that we also stabilize static _: TYPE = EXPR;
, so as to keep const
and static
items as much as possible in sync...
...I think we should move forward with this.
It clearly solves an important use-case and interactions with associated consts, generic consts, and other patterns is something that I think we can have in the same language. It should not be hard to provide diagnostics that address confusions if they arise.
As for the intention of const _: T = { .. }
being unclear, I agree that it is. But we should remember that this pattern will almost always be hidden behind a macro and there it will be clear __in its proper context__. In any case, in relative terms, it seems clearer and better than:
let q = quote! {
#[allow(non_upper_case_globals)]
const #_const: () = {
...
};
}
I did consider mod _ { ... }
as a replacement for this but I came to the conclusion that const _: T { ... }
is simpler as the latter can be generalized to irrefutable patterns whereas I don't see a generalization for the former.
+1 from me on this feature. This enables many opportunities for better-behaved macros and macros with more convenient input syntax. @roblabla cited some above, and I would love to use this in some of my crates too, including to be able to resolve https://github.com/dtolnay/inventory/issues/8 which I don't know of any other way (existing or proposed) to fix.
I added a checklist item to the top comment to track the implementation of static _
. @dsciarra could I interest you in following up with that modification?
@Centril -- I don't think that static _
adds any expressiveness to what we already get from const _
. That is, it can already be written in the following way which is equivalent as far as a macro would be concerned:
- static _: $T = $V;
+ const _: () = {
+ static STATIC: $T = $V;
+ };
I don't have any objection to supporting static _
though if we find it valuable to keep the grammar in sync between const and static.
@Centril what do you think is the best way to move forward from here? Want to wait for the static _
implementation to land before entering FCP? In the worst case we would find out that the rest of the team is fine with the RFC as written and would not want to extend to static. Alternatively we could do it now with or without simultaneously proposing the modification to support static.
@dtolnay
@Centril -- I don't think that
static _
adds any expressiveness to what we already get fromconst _
.
Yep; there should be no difference wrt. language semantics between const _: $TYPE = $EXPR
and static _: $TYPE = $EXPR
.
I don't have any objection to supporting
static _
though if we find it valuable to keep the grammar in sync between const and static.
This is the intent. We should keep the grammar in sync and avoid adding more conceptual complexity. This also facilitates the conceptual extension to $THING $PAT: $TYPE = $EXPR;
where $THING = static | const ;
.
@Centril what do you think is the best way to move forward from here? Want to wait for the
static _
implementation to land before entering FCP?
Either way works for me but fixing the implementation first is probably best since it makes the stabilization report cleaner and more understandable.
Once the implementation change has been done I'm happy to collaborate (or write, if no one else wants to collaborate) on a stabilization report.
FWIW I am not sure that patterns make sense for static
the same way they do for const
: static
denotes a place, a location in memory, while const
denotes a value. So patterns for static
would always have to be "by-ref", unlike patterns for const
.
Hmm, I just recently unimplemented accidentally implemented static _
recently because it didn't make sense.
It still doesn't make sense, IMO, you never want an unnamed "place".
We can make it parse, but be rejected at semantic level, if you wish consistency at syntactic level.
It still doesn't make sense, IMO, you never want an unnamed "place".
This has not been my experience. For example an unnamed "place" would be super useful for macros when we don't need to refer to a generated static other than through its link_section, such as in https://github.com/dtolnay/linkme or https://github.com/mmastrac/rust-ctor.
Hmm, the C-style constructors are indeed a great example.
@RalfJung
FWIW I am not sure that patterns make sense for
static
the same way they do forconst
:static
denotes a place, a location in memory, whileconst
denotes a value. So patterns forstatic
would always have to be "by-ref", unlike patterns forconst
.
So we can consider let
statements instead then. let (a, b) = (1, 2);
introduces two places for a
and b
in memory and so static (A, B): (u8, u8) = (1, 2);
can presumably do the same for A
and B
. Restrictions around moving out of static
s apply as well as they do today (which is what you meant by "by-ref" I take it). (Similarly we have static (mut A, B) = (1, 2)
but those are eeeew... but at least that brings immediate syntactic unification).
This leaves us with the question of what static (): () = ();
and static _: () = ();
does. It seems to me that these introduce no places in memory and merely evaluate the RHS, pattern match on it, and then drop the ()
s on the floor (which does nothing).
let (a, b) = (1, 2); introduces two places for a and b in memory
That's fair, let
also introduces places. I had not considered that.
From that perspective it actually seems more like const
is the odd one out.
@RalfJung
From that perspective it actually seems more like
const
is the odd one out.
Yeah. The common theme of static
, const
, and let
is that they all could adhere to "irrefutable pattern matching that may introduce bindings".
There's one annoyance with const
items. const mut A: B = 0;
<-- You better ensure that you cannot take a &mut B
to A
. A check already exists for static mut
so that can probably be reused to ensure that you cannot get that &mut B
(or prevent it at definition site).
@Centril I would expect mut
would be disallowed in "const
patterns", as the names being declared are values (very similar to fn
items) and not places.
@eddyb Sure; that also works and has the same practical outcome.
How does this interact with rustdoc? Would anonymous constants show up in the documentation?
Currently, the constants show up as _ in the documentation. https://sunriseos.github.io/SunriseOS/master/sunrise_ahci/hba/index.html#constants
I think we should stabilize this as soon as its implemented; I'd expect statics and consts to both be irrefutable pattern positions. I don't think its important to keep statics and consts the same; I think its already inconsistent that they don't take irrefutable patterns, and bringing one of them closer to that now is better than waiting until they are both at feature parity and moving them together. In particular, unnamed statics have real semantic questions as to whether the place gets created or not.
If the _
is interpreted as a pattern, and not as a unique identifier like in use foo as _;
, then the implementation needs to be changed to not add the names of _
consts into their parent modules.
let _ =
is a weird beast, so the following sticks out from the RFC:
Just like let _, this doesn't introduce any new bindings, but still evaluates the rvalue at compile time like any other constant.
That's not "just like let _
". let _
evaluates the place but never performs the place-to-rvalue coercion. Specifically, let _ = *v
does not access memory, and let _ = mutex.lock().unwrap()
does not drop the guard.
It is unfortunate that Rust was designed that way and we do not have a way back. It is really confusing to even experienced users unless they jump into the very deep level of the language.
If this feature could ever be designed again, I would hope that let _ = ...
to always performs the place-to-rvalue coercion unless it is no-op, so we have less weirdness.
That's not "just like
let _
".let _
evaluates the _place_ but never performs the place-to-rvalue coercion. Specifically,let _ = *v
does not access memory, andlet _ = mutex.lock().unwrap()
does not drop the guard.
Specifically,
let _ = *v
does not access memory, andlet _ = mutex.lock().unwrap()
does not drop the guard.
Did you mean that it does drop the guard?
Dang, it got me again... this does not drop though:
fn main() {
let mutex = Mutex::new(());
let val = mutex.lock().unwrap();
let _ = val; // does not drop nor move.
let _ = mutex.lock().unwrap();
}
That's not "just like
let _
".let _
evaluates the _place_ but never performs the place-to-rvalue coercion. Specifically,let _ = *v
does not access memory, andlet _ = mutex.lock().unwrap()
does not drop the guard.
I think the confusion here is the direction of the conversion. Matching against a pattern always does a "value to place" conversion, as patterns require places to do discriminant checks and field accesses, so:
let _ = place_expr;
evaluates but doesn't access the place, so it's close to &raw place_expr
let _ = value_expr;
evaluates the value, puts it in a temporary place, which gets dropped, so identical in behavior to drop(value_expr);
, value_expr;
or many other uses of a value expression where the value gets dropped - including &raw place_expr;
!So depending on some exact details around unsafe code and whatnot, let _ = e;
might be equivalent to &raw e;
in all cases (ignoring optimizations, or assuming unused address-of are ignored).
If the
_
is interpreted as a pattern, and not as a unique identifier like inuse foo as _;
, then the implementation needs to be changed to not add the names of_
consts into their parent modules.
I think it would be good to fix this so that we treat const _: T = expr;
as we do with extern { .. }
and foo!();
in an item position right now. That is, it doesn't have a name, and we don't gensym. That might perhaps involve having Option<Ident>
in the hir::Item
.
You don't need Option
because extern {...}
and foo!();
already work with ident: Ident
in {ast,hir}::Item
(via the "Invalid
" "keyword", aka Symbol::intern("")
).
@petrochenkov Are there any observable differences between _
as PatKind::Wild
and using gensyms as it is implemented right now (aside from rustdoc which isn't important here) such that it matters wrt. stability and forward compatibility?
Are there any observable differences between _ as PatKind::Wild and using gensyms as it is implemented right now
No, there should be no observable difference after https://github.com/rust-lang/rust/pull/56392.
They just create some extra work for the resolver and also encoder/decoder, I think.
We discussed this on the language team meeting today and gave the green light for a stabilization report and PR (see above). I will be writing the report and adding it to the PR within a few hours.
Most helpful comment
I'd start out with both being disallowed, since that is the current behaviour of constants. But I have no strong opinion.