Skip to content

Commit dc29300

Browse files
[WIP] Announce if and match in constants
1 parent e6dd268 commit dc29300

File tree

1 file changed

+151
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)