Skip to content

Commit dfff7a6

Browse files
authored
Merge pull request #461 from ecstatic-morse/patch-1
Announce `if` and `match` in constants on nightly
2 parents cc0475b + ff2ad5b commit dfff7a6

File tree

1 file changed

+164
-0
lines changed

1 file changed

+164
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
---
2+
layout: post
3+
title: "`if` and `match` in constants on nightly rust"
4+
author: Dylan MacKenzie
5+
team: WG const-eval <https://github.com/rust-lang/const-eval>
6+
---
7+
8+
**TLDR; `if` and `match` are now usable in constants on the latest nightly.**
9+
10+
As a result, you can now write code like the following and have it execute at
11+
compile-time:
12+
13+
```rust
14+
static PLATFORM: &str = if cfg!(unix) {
15+
"unix"
16+
} else if cfg!(windows) {
17+
"windows"
18+
} else {
19+
"other"
20+
};
21+
22+
const _: () = assert!(std::mem::size_of::<usize>() == 8, "Only 64-bit platforms are supported");
23+
```
24+
25+
`if` and `match` can also be used in the body of a `const fn`:
26+
27+
```rust
28+
const fn gcd(a: u32, b: u32) -> u32 {
29+
match (a, b) {
30+
(x, 0) | (0, x) => x,
31+
32+
(x, y) if x % 2 == 0 && y % 2 == 0 => 2*gcd(x/2, y/2),
33+
(x, y) | (y, x) if x % 2 == 0 => gcd(x/2, y),
34+
35+
(x, y) if x < y => gcd((y-x)/2, x),
36+
(x, y) => gcd((x-y)/2, y),
37+
}
38+
}
39+
```
40+
41+
## What exactly is going on here?
42+
43+
The following expressions,
44+
- `match`
45+
- `if` and `if let`
46+
- `&&` and `||`
47+
48+
can now appear in any of the following contexts,
49+
- `const fn` bodies
50+
- `const` and associated `const` initializers
51+
- `static` and `static mut` initializers
52+
- array initializers
53+
- const generics (EXPERIMENTAL)
54+
55+
if `#![feature(const_if_match)]` is enabled for your crate.
56+
57+
You may have noticed that the short-circuiting logic operators, `&&` and
58+
`||`, were already legal in a `const` or `static`. This was accomplished by
59+
translating them to their non-short-circuiting equivalents, `&` and `|`
60+
respectively. Enabling the feature gate will turn off this hack and make `&&`
61+
and `||` behave as you would expect.
62+
63+
As a side-effect of these changes, the `assert` and `debug_assert` macros
64+
become usable in a const context if `#![feature(const_panic)]` is also
65+
enabled. However, the other assert macros (e.g., `assert_eq`,
66+
`debug_assert_ne`) remain forbidden, since they need to call `Debug::fmt` on
67+
their arguments.
68+
69+
The looping constructs, `while`, `for`, and `loop` are also forbidden and will
70+
be [feature-gated separately][52000]. As you have seen above, loops can be
71+
emulated with recursion as a temporary measure. However, the non-recursive
72+
version will usually be more efficient since rust does not (to my knowledge)
73+
do tail call optimization.
74+
75+
Finally, the `?` operator remains forbidden in a const context, since its
76+
desugaring contains a call to `From::from`. The design for `const` trait
77+
methods is still being discussed, and both `?` and `for`, which desugars to a
78+
call to `IntoIterator::into_iter`, will not be usable until a final decision is
79+
reached.
80+
81+
[52000]: https://github.com/rust-lang/rust/issues/52000
82+
83+
## What's next?
84+
85+
This change will allow a great number of standard library functions to be made
86+
`const`. You can help with this process! To get started, here's a [list of
87+
numeric functions][const-int] that can be constified with little effort.
88+
Conversion to a `const fn` requires two steps. First, `const` is added to a
89+
function definition along with a `#[rustc_const_unstable]` attribute. This
90+
allows nightly users to call it in a const context. Then, after a period of
91+
experimentation, the attribute is removed and the constness of that function is
92+
stabilized. See [#61635] for an example of the first step and [#64028] for an
93+
example of the second.
94+
95+
Personally, I've looked forward to this feature for a long time, and I can't
96+
wait to start playing with it. If you feel the same, I would greatly
97+
appreciate if you tested the limits of this feature! Try to sneak `Cell`s and
98+
types with `Drop` impls into places they shouldn't be allowed, blow up the
99+
stack with poorly implemented recursive functions (see `gcd` above), and let
100+
us know if something goes horribly wrong.
101+
102+
[const-int]: https://github.com/rust-lang/rust/issues/53718
103+
[#61635]: https://github.com/rust-lang/rust/issues/61635
104+
[#64028]: https://github.com/rust-lang/rust/pull/64028
105+
106+
## What took you so long?
107+
108+
[The Miri engine][miri], which rust uses under the hood for compile-time
109+
function evaluation, has been capable of this for a while now. However, rust
110+
needs to statically guarantee certain properties about variables in a `const`,
111+
such as whether they allow for interior mutability or whether they have a
112+
`Drop` implementation that needs to be called. For example, we must reject the
113+
following code since it would result in a `const` being mutable at runtime!
114+
115+
[miri]: https://rust-lang.github.io/rustc-guide/miri.html
116+
117+
```rust
118+
const CELL: &std::cell::Cell<i32> = &std::cell::Cell::new(42); // Not allowed...
119+
120+
fn main() {
121+
CELL.set(0);
122+
println!("{}", CELL.get()); // otherwise this could print `0`!!!
123+
}
124+
```
125+
126+
However, it is sometimes okay for a `const` to contain a reference to a *type*
127+
that may have interior mutability, as long as we can prove that the actual
128+
*value* of that type does not. This is particularly useful for `enum`s with a
129+
"unit variant" (e.g., `Option::None`).
130+
131+
```rust
132+
const NO_CELL: Option<&std::cell::Cell<i32>> = None; // OK
133+
```
134+
135+
A more detailed (but non-normative) treatment of the rules [for `Drop`][drop]
136+
and [for interior mutability][interior-mut] in a const context can be found
137+
on the [`const-eval`] repo.
138+
139+
It is not trivial to guarantee properties about the value of a variable when
140+
complex control flow such as loops and conditionals is involved. Implementing
141+
this feature required extending the existing dataflow framework in rust so
142+
that we could properly track the value of each local across the control-flow
143+
graph. At the moment, the analysis is very conservative, especially when values are
144+
moved in and out of compound data types. For example, the following will not
145+
compile, even when the feature gate is enabled.
146+
147+
```rust
148+
const fn imprecise() -> Vec<i32> {
149+
let tuple: (Vec<i32>) = (Vec::new(),);
150+
tuple.0
151+
}
152+
```
153+
154+
Even though the `Vec` created by `Vec::new` will never actually be dropped
155+
inside the `const fn`, we don't detect that all fields of `tuple` have been moved
156+
out of, and thus conservatively assume that the drop impl for `tuple` will run.
157+
While this particular case is trivial, there are other, more complex ones that
158+
would require a more comprehensive solution. It is an open question how precise
159+
we want to be here, since more precision means longer compile times, even for
160+
users that have no need for more expressiveness.
161+
162+
[`const-eval`]: https://github.com/rust-lang/const-eval
163+
[drop]: https://github.com/rust-lang/const-eval/blob/master/static.md#drop
164+
[interior-mut]: https://github.com/rust-lang/const-eval/blob/master/const.md#2-interior-mutability

0 commit comments

Comments
 (0)