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