|
| 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