Skip to content

Commit 49dba2b

Browse files
committed
Merge remote-tracking branch 'ecstatic-morse/inline-const'
2 parents 68e17ac + ee9164a commit 49dba2b

File tree

1 file changed

+342
-0
lines changed

1 file changed

+342
-0
lines changed

text/0000-inline-const.md

+342
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
- Feature Name: `inline_const`
2+
- Start Date: 2020-04-30
3+
- RFC PR: [rust-lang/rfcs#2920](https://github.com/rust-lang/rfcs/pull/2920)
4+
- Rust Issue: TBD
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Adds a new syntactical element called an "inline `const`", written as
10+
`const { ... }`, which instructs the compiler to execute the contents of the
11+
block at compile-time. An inline `const` can be used as an expression or
12+
anywhere in a pattern where a named `const` would be allowed.
13+
14+
```rust
15+
use std::net::Ipv6Addr;
16+
17+
fn mock_ip(use_localhost: bool) -> &'static Ipv6Addr {
18+
if use_localhost {
19+
&Ipv6Addr::LOCALHOST
20+
} else {
21+
const { &Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0) }
22+
}
23+
}
24+
25+
const MMIO_BIT1: u8 = 4;
26+
const MMIO_BIT2: u8 = 5;
27+
28+
fn main() {
29+
match read_mmio() {
30+
0 => {}
31+
const { 1 << MMIO_BIT1 } => println!("FOO"),
32+
const { 1 << MMIO_BIT2 } => println!("BAR"),
33+
34+
_ => unreachable!(),
35+
}
36+
}
37+
```
38+
39+
# Motivation
40+
[motivation]: #motivation
41+
42+
Rust has `const` items, which are guaranteed to be initialized at compile-time.
43+
Because of this, they can do things that normal variables cannot. For example,
44+
a reference in a `const` initializer has the `'static` lifetime, and a `const`
45+
can be used as an array initializer even if the type of the array is not
46+
`Copy` (with [RFC 2203]).
47+
48+
[RFC 2203]: https://github.com/rust-lang/rfcs/pull/2203
49+
50+
```rust
51+
fn foo(x: &i32) -> &i32 {
52+
const ZERO: &'static i32 = &0;
53+
if *x < 0 { ZERO } else { x }
54+
}
55+
56+
57+
fn foo() -> &u32 {
58+
const RANGE: Range<i32> = 0..5; // `Range` is not `Copy`
59+
let three_ranges = [RANGE; 3];
60+
}
61+
```
62+
63+
Writing out a `const` declaration every time we need a long-lived reference or
64+
a non-`Copy` array initializer can be annoying. To improve the situation,
65+
[RFC 1414] introduced rvalue static promotion to extend lifetimes, and
66+
[RFC 2203] extended the concept of promotion to array initializers.
67+
As a result, the previous example can be written more concisely.
68+
69+
[RFC 1414]: https://github.com/rust-lang/rfcs/pull/2203
70+
71+
```rust
72+
fn foo(x: &i32) -> &i32 {
73+
if *x < 0 { &0 } else { x }
74+
}
75+
76+
fn foo() -> &u32 {
77+
let three_ranges = [0..5; 3];
78+
}
79+
```
80+
81+
However, the fact that we are executing the array initializer or expression
82+
after the `&` at compile-time is not obvious to the user. To avoid violating
83+
their assumptions, we are very careful to promote only in cases where the user
84+
cannot possibly tell that their code is not executing at runtime. This means a
85+
[long list of rules][prom-rules] for determining the promotability of expressions, and it
86+
means expressions that call a `const fn` or that result in a type with a `Drop`
87+
impl need to use a named `const` declaration.
88+
89+
[prom-rules]: https://github.com/rust-lang/const-eval/blob/master/promotion.md#promotability
90+
91+
# Guide-level explanation
92+
[guide-level-explanation]: #guide-level-explanation
93+
94+
This proposal is a middle ground, which is less verbose than named constants
95+
but more obvious and expressive than promotion. In expression context, it
96+
behaves much like the user had written the following, where `Ty` is the
97+
inferred type of the code within the inline `const` expression (represented by
98+
the ellipsis):
99+
100+
```rust
101+
{ const UNIQUE_IDENT: Ty = ...; UNIQUE_IDENT }
102+
```
103+
104+
With this extension to the language, users can ensure that their code executes
105+
at compile-time without needing to declare a separate `const` item that is only
106+
used once.
107+
108+
```rust
109+
fn foo(x: &i32) -> &i32 {
110+
if *x < 0 { const { &4i32.pow(4) } } else { x }
111+
}
112+
113+
fn foo() -> &u32 {
114+
let three_ranges = [const { (0..=5).into_inner() }; 3];
115+
}
116+
```
117+
118+
## Patterns
119+
120+
Patterns are another context that require a named `const` when using complex
121+
expressions. Unlike in the expression context, where promotion is sometimes
122+
applicable, there is no other choice here.
123+
124+
```rust
125+
fn foo(x: i32) {
126+
const CUBE: i32 = 3.pow(3);
127+
match x {
128+
CUBE => println!("three cubed"),
129+
_ => {}
130+
}
131+
}
132+
```
133+
134+
If that `const` is only used inside a single pattern, writing the code using an
135+
inline `const` block makes it easier to scan.
136+
137+
```rust
138+
fn foo(x: i32) {
139+
match x {
140+
const { 3.pow(3) } => println!("three cubed"),
141+
_ => {}
142+
}
143+
}
144+
```
145+
146+
# Reference-level explanation
147+
[reference-level-explanation]: #reference-level-explanation
148+
149+
This RFC extends the [grammar for expressions] to be,
150+
151+
[grammar for expressions]: https://doc.rust-lang.org/stable/reference/expressions.html#expressions
152+
153+
> ```
154+
> ExpressionWithBlock :
155+
> OuterAttribute*†
156+
> (
157+
> BlockExpression
158+
> | AsyncBlockExpression
159+
> | UnsafeBlockExpression
160+
> | ConstBlockExpression // new
161+
> | LoopExpression
162+
> | IfExpression
163+
> | IfLetExpression
164+
> | MatchExpression
165+
> )
166+
>
167+
> ConstBlockExpression: `const` BlockExpression // new
168+
> ```
169+
170+
This RFC extends the [grammar for patterns] to be,
171+
172+
[grammar for patterns]: https://doc.rust-lang.org/stable/reference/patterns.html
173+
174+
> ```
175+
> Pattern :
176+
> LiteralPattern
177+
> | IdentifierPattern
178+
> | WildcardPattern
179+
> | RangePattern
180+
> | ReferencePattern
181+
> | StructPattern
182+
> | TupleStructPattern
183+
> | TuplePattern
184+
> | GroupedPattern
185+
> | SlicePattern
186+
> | PathPattern
187+
> | MacroInvocation
188+
> | ConstBlockExpression // new
189+
>
190+
> RangePatternBound :
191+
> CHAR_LITERAL
192+
> | BYTE_LITERAL
193+
> | -? INTEGER_LITERAL
194+
> | -? FLOAT_LITERAL
195+
> | PathInExpression
196+
> | QualifiedPathInExpression
197+
> | ConstBlockExpression // new
198+
> ```
199+
200+
In both the expression and pattern context, an inline `const` behaves as if the
201+
user had declared a uniquely named constant in the containing scope and
202+
referenced it.
203+
204+
## Generic Parameters
205+
206+
For now, inline `const` expressions and patterns cannot refer to in-scope
207+
generic parameters. As of this writing, the same restriction applies to array
208+
length expressions, which seem like a good precedent for this RFC. As far as I
209+
know, this is only a temporary restriction; the long-term goal is to allow
210+
array length expressions to use generic parameters. When this happens, inline
211+
`const` expressions and patterns will also be allowed to refer to in-scope
212+
generics.
213+
214+
```rust
215+
fn foo<T>() {
216+
let x = [4i32; std::mem::size_of::<T>()]; // NOT ALLOWED (for now)
217+
let x = const { std::mem::size_of::<T>() }; // NOT ALLOWED (for now)
218+
}
219+
```
220+
221+
## Containing `unsafe`
222+
223+
At present, containing `unsafe` blocks do not apply to array length expressions inside:
224+
225+
```rust
226+
fn bar() {
227+
let x = unsafe {
228+
[4i32; std::intrinsics::unchecked_add(2i32, 3i32)] // ERROR
229+
};
230+
}
231+
```
232+
233+
I find this somewhat strange, but consistency is important, so inline `const`
234+
expressions should behave the same way. The following would also fail to
235+
compile:
236+
237+
```rust
238+
fn bar() {
239+
let x = unsafe {
240+
const { std::intrinsics::unchecked_add(2i32, 3i32) } // ERROR
241+
};
242+
}
243+
```
244+
245+
If [#72359] is considered a bug and resolved, that change would also apply to
246+
inline `const` expressions and patterns.
247+
248+
[#72359]: https://github.com/rust-lang/rust/issues/72359
249+
250+
# Drawbacks
251+
[drawbacks]: #drawbacks
252+
253+
This excludes other uses of the `const` keyword in expressions and patterns.
254+
I'm not aware of any other proposals that would take advantage of this.
255+
256+
This would also be the first use of type inference for const initializers. Type
257+
inference for named constants was proposed in [RFC 1349]. I don't believe the
258+
blockers for this were technical, so I think this is possible.
259+
260+
[RFC 1349]: https://github.com/rust-lang/rfcs/issues/1349
261+
262+
# Rationale and alternatives
263+
[rationale-and-alternatives]: #rationale-and-alternatives
264+
265+
The main alternative is the status quo. Maintaining it will likely result in
266+
promotion being used for more contexts. The lang-team decided to [explore this
267+
approach](https://github.com/rust-lang/rust/pull/70042#issuecomment-612221597)
268+
instead.
269+
270+
It would also possible to separate out the parts of this RFC relating to patterns
271+
so that they can be decided upon separately.
272+
273+
# Prior art
274+
[prior-art]: #prior-art
275+
276+
Zig has the `comptime` keyword that [works similarly][zig] when it appears
277+
before a block.
278+
279+
I'm not aware of equivalents in other languages.
280+
281+
AFAIK, this was [first proposed] by **@scottmcm**.
282+
283+
[zig]: https://kristoff.it/blog/what-is-zig-comptime/#compile-time-function-calls
284+
[first proposed]: https://internals.rust-lang.org/t/quick-thought-const-blocks/7803/9
285+
286+
# Unresolved questions
287+
[unresolved-questions]: #unresolved-questions
288+
289+
## Naming
290+
291+
I prefer the name inline `const`, since it signals that there is no difference
292+
between a named `const` and an inline one.
293+
294+
**@scottmcm** prefers "`const` block", which is closer to the syntax and parallels
295+
the current terminology of `async` block and `unsafe` block. It also avoids any
296+
accidental conflation with the `#[inline]` attribute, which is unrelated.
297+
Additionally, it doesn't extend nicely to the single-expression variant
298+
discussed in [future possibilities].
299+
300+
**@RalfJung** prefers "anonymous `const`". **@scottmcm** mentioned in Zulip
301+
that this could be confused with the `const _: () = ...;` syntax introduced in
302+
[RFC 2526]. The reference refers to these as "unnamed" constants.
303+
304+
[RFC 2526]: https://github.com/rust-lang/rfcs/pull/2526
305+
306+
## Lints about placement of inline `const`
307+
308+
An inline `const` is eligible for promotion in an implicit context (just like a
309+
named `const`), so the following are all guaranteed to work:
310+
311+
```rust
312+
let x: &'static i32 = &const { 4i32.pow(4) };
313+
let x: &'static i32 = const { &4i32.pow(4) };
314+
315+
// If RFC 2203 is stabilized
316+
let v = [const { Vec::new() }; 3];
317+
let v = const { [Vec::new(); 3] };
318+
```
319+
320+
I don't have strong feelings about which version should be preferred.
321+
**@RalfJung** points out that `&const { 4 + 2 }` is more readable than `const {
322+
&(4 + 2) }`.
323+
324+
Note that it may be possible for RFC 2203 to use the explicit rules for
325+
promotability when `T: !Copy`. In this case, the last part of the example above
326+
could simply be written as `[Vec::new(); 3]`.
327+
328+
Inline `const`s are allowed within `const` and `static` initializers, just as we
329+
currently allow nested `const` declarations. Whether to lint against inline
330+
`const` expressions inside a `const` or `static` is also an open question.
331+
332+
# Future possibilities
333+
[future possibilities]: #future-possibilities
334+
335+
It would be possible to allow the syntax `const expr` for an inline `const` that
336+
consists of a single expression. This is analogous to the single expression
337+
variant of closures: `|| 42`. This is backwards compatible with the current proposal.
338+
339+
At some point (an edition boundary?), we may want to narrow the scope of
340+
expressions that are eligible for implicit promotion. Inline `const`
341+
expressions would be the recommended replacement for expressions that were no
342+
longer eligible.

0 commit comments

Comments
 (0)