From 0fde6ec974aeb942f7aa49b88bb5693cb0209d01 Mon Sep 17 00:00:00 2001 From: ecstatic-morse Date: Tue, 19 Nov 2019 17:48:26 -0800 Subject: [PATCH 01/16] Announce `if` and `match` in constants --- .../inside-rust/2019-11-23-const-if-match.md | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 posts/inside-rust/2019-11-23-const-if-match.md diff --git a/posts/inside-rust/2019-11-23-const-if-match.md b/posts/inside-rust/2019-11-23-const-if-match.md new file mode 100644 index 000000000..4f4624908 --- /dev/null +++ b/posts/inside-rust/2019-11-23-const-if-match.md @@ -0,0 +1,151 @@ +--- +layout: post +title: "`if` and `match` in constants on nightly rust" +author: Dylan MacKenzie +--- + +**TLDR; `if` and `match` are now usable in constants on the latest nightly.** + +As a result, you can now write code like the following and have it execute at +compile-time: + +```rust +static PLATFORM: &str = if cfg!(unix) { + "unix" +} else if cfg!(windows) { + "windows" +} else { + "other" +}; + +const fn gcd(a: u32, b: u32) -> u32 { + match (a, b) { + (x, 0) | (0, x) => x, + + (x, y) if x % 2 == 0 && y % 2 == 0 => 2*gcd(x/2, y/2), + (x, y) | (y, x) if x % 2 == 0 => gcd(x/2, y), + + (x, y) if x < y => gcd((y-x)/2, x), + (x, y) => gcd((x-y)/2, y), + } +} + +const _: () = assert!(std::mem::size_of::(), 8, "Only 64-bit platforms are supported"); +``` + +## What exactly is going on here? + +The following expressions, +- `match` +- `if` and `if let` +- `&&` and `||` + +can now appear in any of the following contexts, +- `const fn` bodies +- `const` and associated `const` initializers +- `static` and `static mut` initializers +- array initializers +- const generics (EXPERIMENTAL) + +if `#![feature(const_if_match)]` is enabled for your crate. + +You may have noticed that the short-circuiting logic operators, `&&` and +`||`, were already legal in a `const` or `static`. This was accomplished by +translating them to their non-short-circuiting equivalents, `&` and `|` +respectively. Enabling the feature gate will turn off this hack and make `&&` +and `||` behave as you would expect. + +As a side-effect of these changes, the `assert` and `debug_assert` macros +become usable in a const context if `#![feature(const_panic)]` is also +enabled. However, the other assert macros (e.g., `assert_eq`, +`debug_assert_ne`) remain forbidden, since they need to call `Debug::fmt` on +their arguments. + +Also forbidden are looping constructs, `while`, `for` and `loop`, which will +be [feature-gated separately][52000], and the `?` operator, which calls +`From::from` on the value inside the `Err` variant. The design for +`const` trait methods is still being discussed. + +[52000]: https://github.com/rust-lang/rust/issues/52000 + +## What's next? + +This change will allow a great number of standard library functions to be +made `const`. If you like, you can help with this process! There's a [list of +numeric functions][const-int] that can be constified with little effort. Be +aware that things move slowly in the standard library, and it's always best +to have a concrete use case that demonstrates why this *particular* function +needs to be callable in a const context. + +Personally, I've looked forward to this feature for a long time, and I can't +wait to start playing with it. If you feel the same, I would greatly +appreciate if you tested the limits of this feature! Try to sneak `Cell`s and +types with `Drop` impls into places they shouldn't be allowed, blow up the +stack with poorly implemented recursive functions (see `gcd` above), and let +us know if something goes horribly wrong. + +[const-int]: https://github.com/rust-lang/rust/issues/53718 + +## What took you so long? + +[Miri], which rust uses under the hood for compile-time function evaluation, +has been capable of this for a while now. However, rust needs to statically +guarantee certain properties about variables in a `const`, such as whether +they allow for interior mutability or whether they have a `Drop` +implementation that needs to be called. For example, we must reject the +following code since it would result in a `const` being mutable at runtime! + +[Miri]: https://github.com/rust-lang/miri + +```rust +const CELL: &std::cell::Cell = &std::cell::Cell::new(42); // Not allowed... + +fn main() { + CELL.set(0); + println!("{}", CELL.get()); // otherwise this could print `0`!!! +} +``` + +However, it is sometimes okay for a `const` to contain a *type* that allows +interior mutability, as long as we can prove that the actual *value* of that +type does not. This is particularly useful for `enum`s with a "unit variant" +(e.g., `Option::None`). + +```rust +const NO_CELL: Option<&std::cell::Cell> = None; // OK +``` + +A more detailed (but non-normative) treatment of the rules [for `Drop`][drop] +and [for interior mutability][interior-mut] in a const context can be found +on the [`const-eval`] repo. + +It is not trivial to guarantee properties about the value of a variable when +complex control flow such as loops and conditionals is involved. Implementing +this feature required extending the existing dataflow framework in rust so +that we could properly track the value of each local across the control-flow +graph. At the moment, the analysis is very conservative, especially when values are +moved in and out of compound data types. For example, the following will not +compile, even when the feature gate is enabled. + +```rust +const fn imprecise() -> Vec { + let tuple: (Vec) = (Vec::new(),); + tuple.0 +} +``` + +Even though the `Vec` created by `Vec::new` will never actually be dropped +inside the `const fn`, we don't detect that all fields of `tuple` have been moved +out of, and thus conservatively assume that the drop impl for `tuple` will run. +While this particular case is trivial, there are other, more complex ones that +would require a more expensive solution. It is an open question how precise we +want to be here. + +[`const-eval`]: https://github.com/rust-lang/const-eval +[drop]: https://github.com/rust-lang/const-eval/blob/master/static.md#drop +[interior-mut]: +https://github.com/rust-lang/const-eval/blob/master/const.md#2-interior-mutability + +## Acknowledgements + +TODO From 7f362641fd7069c5fb3c52a191360e7efe0d002d Mon Sep 17 00:00:00 2001 From: ecstatic-morse Date: Wed, 20 Nov 2019 10:08:05 -0800 Subject: [PATCH 02/16] Fix `assert` example Co-Authored-By: lzutao --- posts/inside-rust/2019-11-23-const-if-match.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posts/inside-rust/2019-11-23-const-if-match.md b/posts/inside-rust/2019-11-23-const-if-match.md index 4f4624908..eab852578 100644 --- a/posts/inside-rust/2019-11-23-const-if-match.md +++ b/posts/inside-rust/2019-11-23-const-if-match.md @@ -30,7 +30,7 @@ const fn gcd(a: u32, b: u32) -> u32 { } } -const _: () = assert!(std::mem::size_of::(), 8, "Only 64-bit platforms are supported"); +const _: () = assert!(std::mem::size_of::() == 8, "Only 64-bit platforms are supported"); ``` ## What exactly is going on here? From 35924220ecee0c560390350642c2d03a70610baa Mon Sep 17 00:00:00 2001 From: ecstatic-morse Date: Wed, 20 Nov 2019 10:16:02 -0800 Subject: [PATCH 03/16] I've seen those English dramas, too Co-Authored-By: Mazdak Farrokhzad --- posts/inside-rust/2019-11-23-const-if-match.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posts/inside-rust/2019-11-23-const-if-match.md b/posts/inside-rust/2019-11-23-const-if-match.md index eab852578..ee19010d7 100644 --- a/posts/inside-rust/2019-11-23-const-if-match.md +++ b/posts/inside-rust/2019-11-23-const-if-match.md @@ -61,7 +61,7 @@ enabled. However, the other assert macros (e.g., `assert_eq`, `debug_assert_ne`) remain forbidden, since they need to call `Debug::fmt` on their arguments. -Also forbidden are looping constructs, `while`, `for` and `loop`, which will +Also forbidden are looping constructs, `while`, `for`, and `loop`, which will be [feature-gated separately][52000], and the `?` operator, which calls `From::from` on the value inside the `Err` variant. The design for `const` trait methods is still being discussed. From fb2ab592b668f06d959d230c911d5bed21f4eb85 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Wed, 20 Nov 2019 10:36:34 -0800 Subject: [PATCH 04/16] Add `team` to front matter --- posts/inside-rust/2019-11-23-const-if-match.md | 1 + 1 file changed, 1 insertion(+) diff --git a/posts/inside-rust/2019-11-23-const-if-match.md b/posts/inside-rust/2019-11-23-const-if-match.md index ee19010d7..6cb691653 100644 --- a/posts/inside-rust/2019-11-23-const-if-match.md +++ b/posts/inside-rust/2019-11-23-const-if-match.md @@ -2,6 +2,7 @@ layout: post title: "`if` and `match` in constants on nightly rust" author: Dylan MacKenzie +team: WG const-eval --- **TLDR; `if` and `match` are now usable in constants on the latest nightly.** From 23748d5d79408bf413dfbd72b87f49c9a68207f0 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Wed, 20 Nov 2019 10:37:00 -0800 Subject: [PATCH 05/16] Split out `const fn` example --- posts/inside-rust/2019-11-23-const-if-match.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/posts/inside-rust/2019-11-23-const-if-match.md b/posts/inside-rust/2019-11-23-const-if-match.md index 6cb691653..e6f7cdfb6 100644 --- a/posts/inside-rust/2019-11-23-const-if-match.md +++ b/posts/inside-rust/2019-11-23-const-if-match.md @@ -19,6 +19,12 @@ static PLATFORM: &str = if cfg!(unix) { "other" }; +const _: () = assert!(std::mem::size_of::() == 8, "Only 64-bit platforms are supported"); +``` + +`if` and `match` can also be used in the body of a `const fn`: + +```rust const fn gcd(a: u32, b: u32) -> u32 { match (a, b) { (x, 0) | (0, x) => x, @@ -30,8 +36,6 @@ const fn gcd(a: u32, b: u32) -> u32 { (x, y) => gcd((x-y)/2, y), } } - -const _: () = assert!(std::mem::size_of::() == 8, "Only 64-bit platforms are supported"); ``` ## What exactly is going on here? From 07782e8eca40a43ab7ee0fd804250cb03af4acb7 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Wed, 20 Nov 2019 10:37:25 -0800 Subject: [PATCH 06/16] Reword call for participation --- posts/inside-rust/2019-11-23-const-if-match.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/posts/inside-rust/2019-11-23-const-if-match.md b/posts/inside-rust/2019-11-23-const-if-match.md index e6f7cdfb6..23029ebcc 100644 --- a/posts/inside-rust/2019-11-23-const-if-match.md +++ b/posts/inside-rust/2019-11-23-const-if-match.md @@ -75,12 +75,9 @@ be [feature-gated separately][52000], and the `?` operator, which calls ## What's next? -This change will allow a great number of standard library functions to be -made `const`. If you like, you can help with this process! There's a [list of -numeric functions][const-int] that can be constified with little effort. Be -aware that things move slowly in the standard library, and it's always best -to have a concrete use case that demonstrates why this *particular* function -needs to be callable in a const context. +This change will allow a great number of standard library functions to be made +`const`. You can help with this process! To get started, here's a [list of +numeric functions][const-int] that can be constified with little effort. Personally, I've looked forward to this feature for a long time, and I can't wait to start playing with it. If you feel the same, I would greatly From 4b22d2285768b68470f33543499c038cda6ee345 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Wed, 20 Nov 2019 10:37:36 -0800 Subject: [PATCH 07/16] Fix link --- posts/inside-rust/2019-11-23-const-if-match.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/posts/inside-rust/2019-11-23-const-if-match.md b/posts/inside-rust/2019-11-23-const-if-match.md index 23029ebcc..c4b604f66 100644 --- a/posts/inside-rust/2019-11-23-const-if-match.md +++ b/posts/inside-rust/2019-11-23-const-if-match.md @@ -145,8 +145,7 @@ want to be here. [`const-eval`]: https://github.com/rust-lang/const-eval [drop]: https://github.com/rust-lang/const-eval/blob/master/static.md#drop -[interior-mut]: -https://github.com/rust-lang/const-eval/blob/master/const.md#2-interior-mutability +[interior-mut]: https://github.com/rust-lang/const-eval/blob/master/const.md#2-interior-mutability ## Acknowledgements From 1b53e7c3b4fbe5ab83a5892ab9f8bd03d995c840 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Wed, 20 Nov 2019 10:38:29 -0800 Subject: [PATCH 08/16] Remove trailing whitespace --- posts/inside-rust/2019-11-23-const-if-match.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posts/inside-rust/2019-11-23-const-if-match.md b/posts/inside-rust/2019-11-23-const-if-match.md index c4b604f66..1ec8ec693 100644 --- a/posts/inside-rust/2019-11-23-const-if-match.md +++ b/posts/inside-rust/2019-11-23-const-if-match.md @@ -5,7 +5,7 @@ author: Dylan MacKenzie team: WG const-eval --- -**TLDR; `if` and `match` are now usable in constants on the latest nightly.** +**TLDR; `if` and `match` are now usable in constants on the latest nightly.** As a result, you can now write code like the following and have it execute at compile-time: From cbbfaa62eb2e6b5edfea2cb5d3045c80fd7be024 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Wed, 20 Nov 2019 11:07:10 -0800 Subject: [PATCH 09/16] Remove acknowledgements section lest I forget someone --- posts/inside-rust/2019-11-23-const-if-match.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/posts/inside-rust/2019-11-23-const-if-match.md b/posts/inside-rust/2019-11-23-const-if-match.md index 1ec8ec693..193b0df3b 100644 --- a/posts/inside-rust/2019-11-23-const-if-match.md +++ b/posts/inside-rust/2019-11-23-const-if-match.md @@ -146,7 +146,3 @@ want to be here. [`const-eval`]: https://github.com/rust-lang/const-eval [drop]: https://github.com/rust-lang/const-eval/blob/master/static.md#drop [interior-mut]: https://github.com/rust-lang/const-eval/blob/master/const.md#2-interior-mutability - -## Acknowledgements - -TODO From b2fa25c606c76816e54624220ac0b658574940ab Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Wed, 20 Nov 2019 11:37:17 -0800 Subject: [PATCH 10/16] Explain constification process --- posts/inside-rust/2019-11-23-const-if-match.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/posts/inside-rust/2019-11-23-const-if-match.md b/posts/inside-rust/2019-11-23-const-if-match.md index 193b0df3b..11490b11d 100644 --- a/posts/inside-rust/2019-11-23-const-if-match.md +++ b/posts/inside-rust/2019-11-23-const-if-match.md @@ -78,6 +78,12 @@ be [feature-gated separately][52000], and the `?` operator, which calls This change will allow a great number of standard library functions to be made `const`. You can help with this process! To get started, here's a [list of numeric functions][const-int] that can be constified with little effort. +Conversion to a `const fn` requires two steps. First, `const` is added to a +function definition along with a `#[rustc_const_unstable]` attribute. This +allows nightly users to call it in a const context. Then, after a period of +experimentation, the attribute is removed and the constness of that function is +stabilized. See [#61635] for an example of the first step and [#64028] for an +example of the second. Personally, I've looked forward to this feature for a long time, and I can't wait to start playing with it. If you feel the same, I would greatly @@ -87,6 +93,8 @@ stack with poorly implemented recursive functions (see `gcd` above), and let us know if something goes horribly wrong. [const-int]: https://github.com/rust-lang/rust/issues/53718 +[#61635]: https://github.com/rust-lang/rust/issues/61635 +[#64028]: https://github.com/rust-lang/rust/pull/64028 ## What took you so long? From 9652c80fde6c76a7d68aaad3096e392f8aebd4e9 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Fri, 22 Nov 2019 13:24:34 -0800 Subject: [PATCH 11/16] Talk about recursion as a substitute for looping This also separates the discussion of looping constructs from that of `const` trait methods and the `?` operator. --- posts/inside-rust/2019-11-23-const-if-match.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/posts/inside-rust/2019-11-23-const-if-match.md b/posts/inside-rust/2019-11-23-const-if-match.md index 11490b11d..1ee59d83e 100644 --- a/posts/inside-rust/2019-11-23-const-if-match.md +++ b/posts/inside-rust/2019-11-23-const-if-match.md @@ -66,10 +66,17 @@ enabled. However, the other assert macros (e.g., `assert_eq`, `debug_assert_ne`) remain forbidden, since they need to call `Debug::fmt` on their arguments. -Also forbidden are looping constructs, `while`, `for`, and `loop`, which will -be [feature-gated separately][52000], and the `?` operator, which calls -`From::from` on the value inside the `Err` variant. The design for -`const` trait methods is still being discussed. +The looping constructs, `while`, `for`, and `loop` are also forbidden and will +be be [feature-gated separately][52000]. As you have see above, loops can be +emulated with recursion as a temporary measure. However, the non-recursive +version will usually be more efficient since rust does not (to my knowledge) +do tail call optimization. + +Finally, the `?` operator remains forbidden in a const context, since its +desugaring contains a call to `From::from`. The design for `const` trait +methods is still being discussed, and both `?` and `for`, which desugars to a +call to `IntoIterator::into_iter`, will not be usable until a final decision is +reached. [52000]: https://github.com/rust-lang/rust/issues/52000 From 4867fa7db38b5a491ef7bd6ac628d8d14ff8b3e0 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Fri, 22 Nov 2019 13:25:44 -0800 Subject: [PATCH 12/16] Link to Miri engine in rustc guide, not Miri project --- posts/inside-rust/2019-11-23-const-if-match.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/posts/inside-rust/2019-11-23-const-if-match.md b/posts/inside-rust/2019-11-23-const-if-match.md index 1ee59d83e..7275063ab 100644 --- a/posts/inside-rust/2019-11-23-const-if-match.md +++ b/posts/inside-rust/2019-11-23-const-if-match.md @@ -105,14 +105,14 @@ us know if something goes horribly wrong. ## What took you so long? -[Miri], which rust uses under the hood for compile-time function evaluation, -has been capable of this for a while now. However, rust needs to statically -guarantee certain properties about variables in a `const`, such as whether -they allow for interior mutability or whether they have a `Drop` -implementation that needs to be called. For example, we must reject the +[The Miri engine][miri], which rust uses under the hood for compile-time +function evaluation, has been capable of this for a while now. However, rust +needs to statically guarantee certain properties about variables in a `const`, +such as whether they allow for interior mutability or whether they have a +`Drop` implementation that needs to be called. For example, we must reject the following code since it would result in a `const` being mutable at runtime! -[Miri]: https://github.com/rust-lang/miri +[miri]: https://rust-lang.github.io/rustc-guide/miri.html ```rust const CELL: &std::cell::Cell = &std::cell::Cell::new(42); // Not allowed... From 036520535030ee1eabad5c4e5ab7e1c1987cfeea Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Fri, 22 Nov 2019 13:26:05 -0800 Subject: [PATCH 13/16] reference to a *type* --- posts/inside-rust/2019-11-23-const-if-match.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/posts/inside-rust/2019-11-23-const-if-match.md b/posts/inside-rust/2019-11-23-const-if-match.md index 7275063ab..87db5ba76 100644 --- a/posts/inside-rust/2019-11-23-const-if-match.md +++ b/posts/inside-rust/2019-11-23-const-if-match.md @@ -123,10 +123,10 @@ fn main() { } ``` -However, it is sometimes okay for a `const` to contain a *type* that allows -interior mutability, as long as we can prove that the actual *value* of that -type does not. This is particularly useful for `enum`s with a "unit variant" -(e.g., `Option::None`). +However, it is sometimes okay for a `const` to contain a reference to a *type* +that may have interior mutability, as long as we can prove that the actual +*value* of that type does not. This is particularly useful for `enum`s with a +"unit variant" (e.g., `Option::None`). ```rust const NO_CELL: Option<&std::cell::Cell> = None; // OK From 8e257250425290285b2cbcd7c1236735cb433f03 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Fri, 22 Nov 2019 13:27:52 -0800 Subject: [PATCH 14/16] Explicitly mention perf tradeoff with more precise analysis --- posts/inside-rust/2019-11-23-const-if-match.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/posts/inside-rust/2019-11-23-const-if-match.md b/posts/inside-rust/2019-11-23-const-if-match.md index 87db5ba76..3c385ca59 100644 --- a/posts/inside-rust/2019-11-23-const-if-match.md +++ b/posts/inside-rust/2019-11-23-const-if-match.md @@ -155,8 +155,9 @@ Even though the `Vec` created by `Vec::new` will never actually be dropped inside the `const fn`, we don't detect that all fields of `tuple` have been moved out of, and thus conservatively assume that the drop impl for `tuple` will run. While this particular case is trivial, there are other, more complex ones that -would require a more expensive solution. It is an open question how precise we -want to be here. +would require a more comprehensive solution. It is an open question how precise +we want to be here, since more precision means longer compile times, even for +users that have no need for more expressiveness. [`const-eval`]: https://github.com/rust-lang/const-eval [drop]: https://github.com/rust-lang/const-eval/blob/master/static.md#drop From 424762532eb8b370fa80620b4d8b4aeddf0c25a1 Mon Sep 17 00:00:00 2001 From: ecstatic-morse Date: Fri, 22 Nov 2019 13:56:54 -0800 Subject: [PATCH 15/16] Fix typos in new changes Co-Authored-By: lqd --- posts/inside-rust/2019-11-23-const-if-match.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posts/inside-rust/2019-11-23-const-if-match.md b/posts/inside-rust/2019-11-23-const-if-match.md index 3c385ca59..3eed0e30d 100644 --- a/posts/inside-rust/2019-11-23-const-if-match.md +++ b/posts/inside-rust/2019-11-23-const-if-match.md @@ -67,7 +67,7 @@ enabled. However, the other assert macros (e.g., `assert_eq`, their arguments. The looping constructs, `while`, `for`, and `loop` are also forbidden and will -be be [feature-gated separately][52000]. As you have see above, loops can be +be [feature-gated separately][52000]. As you have seen above, loops can be emulated with recursion as a temporary measure. However, the non-recursive version will usually be more efficient since rust does not (to my knowledge) do tail call optimization. From ff2ad5b52b6c5915933de0f2eceb2df69dc5e539 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Mon, 25 Nov 2019 09:59:56 -0800 Subject: [PATCH 16/16] Update post date --- ...{2019-11-23-const-if-match.md => 2019-11-25-const-if-match.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename posts/inside-rust/{2019-11-23-const-if-match.md => 2019-11-25-const-if-match.md} (100%) diff --git a/posts/inside-rust/2019-11-23-const-if-match.md b/posts/inside-rust/2019-11-25-const-if-match.md similarity index 100% rename from posts/inside-rust/2019-11-23-const-if-match.md rename to posts/inside-rust/2019-11-25-const-if-match.md