This is a tracking issue for the RFC "Allow panicking in constants" (rust-lang/rfcs#2345).
Steps:
Unresolved questions:
[ ] Should there be some additional message in the error about this being a panic turned error?
Or do we just produce the exact message the panic would produce?
[ ] This change becomes really useful if Result::unwrap
and Option::unwrap
become const fn
, doing both in one go might be a good idea.
Blockers:
Don't forget expect
and all the other unwrap-like methods. :P
I suppose unimplemented!()
and unreachable!()
should also be included?
I think they would be "automatically", since macros don鈥檛 have a separation similar to fn
v.s. const fn
.
I'll add some tests to #52011, but yes, it just works.
Triage: @oli-obk so this is implemented now, right?
Yes
Now this is implemented, is there anything stopping Option::unwrap
and friends from becoming const fn
s (I'm assuming with a rustc_const_unstable(feature = "??")
)?
@IsaacWoods to my knowledge there shouldn't be anything in the way of that.
@Centril great! I'll start work on a PR in that case
The thing in the way of that is running fmt::Debug::fmt
in const
context.
鈥h, right, I was thinking in terms of Result::unwrap
and Result::expect
. Option
should be fine.
On second thoughts, even Option
is still blocked I think - I don't think we can use match
in const
contexts yet either
Yea, we need conditions in constants first
Right; bummer.
@oli-obk Any thoughts about the first unresolved question?
I'm for basically reporting it verbatim, but having the same prefix (error[0080]:
) as all other errors.
@oli-obk I like that; (and it's a diagnostics issue anyways, not part of the spec, so it is up to T-compiler to decide this...).
@oli-obk I think sufficient time has passed for us to stabilize this... how do you feel about doing that? If you feel alright with that maybe you could write up a stabilization report?
@Centril has this been used in nightly anywhere yet? As far as I'm aware all usecases are blocked on needing control flow, otherwise all this allows is writing un-compilable code (I guess that could be generated by macros/proc-macros, but those already have compile_error!
to use in the same places).
@Nemo157 this basically permits panic!("foo")
at the moment and not much more.
Yea, without control flow this feature is useless except for unimplemented!()
working in const functions
Small question.
Would it allow to have functons like:
const fn static_assert(condition) {
match condition {
true => (),
false => panic!("Assertion failed"),
}
}
No, match
is handled in https://github.com/rust-lang/rust/issues/49146
You can only do const fn foo() { unimplemented!() }
as of right now.
Would this imply that const fn is not pure function?
Panicking doesn't cause impurity. The function will still always act the same for any given input.
I haven't seen any write-ups mentioning const fn would never evolve to be an impure function, so I am assuming you can't rely on this for purity?
I'm fairly certain that constants are by definition pure. What would it mean to have an impure constant?
Imagine a const fn rand_number_gen()
or something. All const
means in my mind is that it runs at compile time. (That said, it's not clear to me how useful non-purity would be).
I feel like impure constants would be a bad idea, as it breaks determinism in builds. That said, I guess that this discussion isn't best had here.
@clarcharr
It may be useful to run iterators at compile time. This would also require impure const fn
.
@porky11 I think you're misunderstanding what impure means. Impure simply means that you get different values for the same input. Mutation isn't impure, as long as it's explicit. If you're passing a mutable reference as an argument (in this case, &mut self
) you can think of it as treating the iterator state as both an input and an output. The same iterator state before will result in the same state after. Therefore, this is still pure.
Panicking is pure because it essentially ends the program when you reach a panic, and the circumstances leading up to it will always be the same. Catching panics could lead to impurity, but that's not mentioned at all here. I don't think that will ever be allowed.
Would be awesome to see compile time assert!(condition)
:)
I believe that this will be possible with the effects traits RFC? On my phone right now but someone can probably share a link.
I actually found out(through people in the IRC) that it's already kind of possible if you cast the bool to usize and use it in indexing &[()][1 - (condition as usize)]
i.e. https://play.rust-lang.org/?gist=962e9178f07a0a534229b6d6b0c37d22
~Yes, but IIUC it produces inefficient code.~
EDIT: oops, misread the comment... ignore me :man_facepalming:
Yeah, there are ways people do compile time assertions but the main issue is that there's no compile-time Debug
.
No code to produce in compile time operations that result in ZST :)
https://godbolt.org/z/1sxuS-
Is there any forwards compatibility concern with eventually being able to catch panics in const fn
?
I think only in the sense that users may expect panics in const eval to cause an error. So if you pass your function f to another function g to be called there, if f is called and panics, you may expect compilation to fail and it now isn't guaranteed, because g may catch the panic.
And we'd have this problem already for index out of bound panics, so there's nothing new
Okay. Our current implementation immediately aborts execution on a panic, we'd have to do something more clever if the panic could be caught... but I assume that should not be impossible.
Are there plans to allow catching panics in CTFE? I thought that feature was only useful/intended for keeping panics from unwinding into FFI, and the RFC doesn't even mention it.
Well I don't see why we'd not eventually permit catching panics in CTFE, given that we plan to allow pretty much everything that doesn't break determinism. ;)
If everything goes well, if and match in constants are going to be released in 1.45 (https://github.com/rust-lang/rust/issues/49146). Is there any significant movement around this issue? I see if/match and panics in constants as a valuable combination: together they bring in the ability for libraries to define const asserts and thus have better story around compile-time guarantees and library-level compiler errors.
Well, there are two unresolved questions and we need to document this feature. I don't see any actual problems with it though.
My two cents about the unresolved questions. Hopefully this stirs some discussion and gets this moving:
Should there be some additional message in the error about this being a panic turned error?
Or do we just produce the exact message the panic would produce?
Currently, the error message is like this ( https://play.rust-lang.org/?version=nightly&mode=debug&edition=2015&gist=1083e1ce78d0ca89e95fb5c3fc733cf5 ):
error[E0080]: evaluation of constant value failed
--> src/main.rs:5:5
|
5 | panic!("panic while evaluating panic_in_const");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'panic while evaluating panic_in_const', src/main.rs:5:5
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: any use of this value will cause an error
--> src/main.rs:8:15
|
8 | const C: () = panic_in_const();
| --------------^^^^^^^^^^^^^^^^-
| |
| referenced constant has errors
|
= note: `#[deny(const_err)]` on by default
error: aborting due to 2 previous errors
I think the error message could be better:
note: this error originates in a macro
seems like a needless thing to say in this case.const_err
lint which I interpret from the error message to allow alternating between "compilation fail right away" and "compilation fail upon a use of the constant". This seems like an useful thing (for macros, at least), and without the second error message, the users wouldn't learn about it. However, when testing it a bit, I can't make heads or tails how it actually works. (For example: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2015&gist=fdc4e6f2212a255b41497781e99c4cea )This change becomes really useful if Result::unwrap and Option::unwrap become const fn, doing both in one go might be a good idea.
- If and match are going to be stabilized, so these APIs are getting unblocked. I think we should go and stabilize their constness as a part of this push.
Note that so far only panics with a constant string are even implemented. That suffices for Option::unwrap
, but Result::unwrap
also formats the error value, and thus is far out of reach of CTFE.
Maybe Result::unwrap
should be left out of the scope then. I half-expected expect
not to require formatting, but forgot that it prints both the expect message and the error value, so no luck there. It should still be possible to unwrap Results
using custom functions, so the capability to do so stays valuable even if unwrap
can't be stabilized right away.
About the first unsolved question: "Or do we just produce the exact message the panic would produce?" I wonder if there's some intention there that panics could be used for bona fide custom-made error messages, and that's why there would be motivation to produce "just the exact message"? As for that, I don't think we should mix and use messages that might have originally meant for run-time error messages, in a different, compile-time context without an explanation about that context. What I mean is that the compiler should clearly say for the reason for the error something like "a constant expression panicked" and then tell the panic error message.
I think that there is need and demand for proper custom-made compiler error messages/warnings in libraries, if the library is able to detect that its API is misused etc. However, the mechanism for that shouldn't be the same than for what is normally meant for run-time panics. It might be that a panicking mechanism is fit for even that, but in that case, the object associated with the panic should be some structured object with an API designed for showing and formatting great compile-time error messages. For purposes of this feature, it's definitely out of scope.
And thus: I think that the answer to the question is: yes, there should be an error message or an explanation that "frames" the error as a panic that happened in a constant. But preferably something more lightweight than what the current message looks like.
As for the documentation: I noticed that mentions about const fn
s are entirely missing from the Reference, which seems to be the relevant place of documentation for feature like this. Have the earlier stabilization PR's been neglecting updating it, or maybe there is so much churn around const fn feature that it makes sense to update it in one go?
mentions about const fns are entirely missing from the Reference
const fn
is documented in the reference https://doc.rust-lang.org/nightly/reference/items/functions.html#const-functions, but the section is slightly out of date https://github.com/rust-lang/reference/issues/800
@memoryruins: Thanks, I missed it.
More about the const_err
: In this example using TEST
inside another constant counts as "use". https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=928d36c50cceec31d97999d5ef05447a
This explains the behaviour I called earlier "seemingly buggy". Using an "erroneous" constant even inside another constant is an error, even though I expected the error propagation logic to work transitively. However, as this example that doesn't use panicking at all shows, the problem isn't directly related to allowing panicking in constants: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=702befa4f5e87e6f997307c8419cbd62
Rather, it's about the propagation and presentation of "erroneous" constants. But with allowing panicking making erroneous constants becomes more normal and expected than before, so it may need to be considered as an UX problem.
and we need to document this feature
The link to the stabilization guide on Forge is broken in the top-level issue. Is there a new reference for writing feature documentation?
@abonander Currently, the stabilization guide is here: https://rustc-dev-guide.rust-lang.org/stabilization_guide.html
DISCLAIMER: there are active discussions about where we want this kind of content to live long-term...
I think that the answer to the question is: yes, there should be an error message or an explanation that "frames" the error as a panic that happened in a constant. But preferably something more lightweight than what the current message looks like.
Anything wrong with just switching the message placement? E.g. from this:
error[E0080]: could not evaluate static initializer
--> src/main.rs:3:21
|
3 | static FOO: usize = panic!("Hello, world!");
| ^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Hello, world!', src/main.rs:3:21
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
To this:
error[E0080]: Hello, world!
--> src/main.rs:3:21
|
3 | static FOO: usize = panic!("Hello, world!");
| ^^^^^^^^^^^^^^^^^^^^^^^ panic in static initializer
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
I don't think there's any ambiguity here. The error is formatted as a compiler-error and the context message explains why it was a compiler error.
I like it. It's radical but powerful. It will just be fun to watch how ppl will use it to emit fake compiler errors by inserting newlines and color characters in their error messages, causing complete confusion (they could have done so with the way we emit messages currently, too, so this is nothing new, they can just do it more conveniently).
Yeah, error[E0080]: could not evaluate static initializer
is not a terribly useful error "title". IMO we should change that for non-panic errors, too, and say more about what went wrong. In Miri, I changed errors to start like
error: unsupported operation: `clock_gettime` not available when isolation is enabled
I like it. It's radical but powerful. It will just be fun to watch how ppl will use it to emit fake compiler errors by inserting newlines and color characters in their error messages, causing complete confusion (they could have done so with the way we emit messages currently, too, so this is nothing new, they can just do it more conveniently).
@oli-obk is that potentially a concern, if people begin to rely on exactly _where_ the compiler emits their const panic message? I guess we can easily disclaim that: "the exact rendering of the panic message is not considered stable" or something like that.
That seems like an unreasonable fear. People can use many parts of the language poorly, and we trust users to avoid crates that give them a bad experience.
But also, yes, the exact rendering should be explicitly classified as a "debug message" and thus subject to potential change without it being a "breaking change".
Is there a reason not to guarantee that panics in constants will display this way? It would be kind of awesome to have a way for people to generate custom error messages...
I would like to not guarantee a specific display because I would like rustc to one day be able to detect color change ansi control codes and strip them out of the output when color output is turned off or automatically detected to be off.
And in general, specifying the exact nature of a debug message just isn't the usual for Rust.
I think saying "the panic message is shown to the user in some way as a compile error" is specific enough.
Even if we don't want to lock ourselves into anything, giving a reasonable default, like the one proposed by @abonander, seems like a good idea.
Could the format string allow specifying the names of the printed variables like in RFC 2795 ? That way one can use specific variables in scope,and put them in any order.
This is all assuming that variables in scope can be printed with the panicking macros, as implied by the guide level explanation of the const-panic RFC.
Currently const-panics only allow string constants, no format string at all. That is still way off at this point I am afraid.
The format string syntax will be exactly the same as for run-time formatting.
Should the error include a stack trace? Otherwise trouble shooting panics that happened deep inside a const fn might be difficult to diagnose. (I don't think this is needed at release, but should be considered as a later enhancement)
We already emit a stack trace: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b0f5cc58e32119df1f116595783db08e
8 | [()][x]
| ^^^^^^^
| |
| index out of bounds: the len is 1 but the index is 4
| inside `foo` at src/main.rs:8:5
| inside `bar` at src/main.rs:4:5
| inside `FOO` at src/main.rs:1:17
There is a workaround for this issue now in stable 1.46 that indexes outside of an array when we want to panic:
// reduced example for my usecase that involves wrappers around `NonZeroUsize`
pub const fn bw(width: usize) -> NonZeroUsize {
["Tried to construct an invalid BitWidth of 0 using the `apint::bw` function"][(width == 0) as usize];
unsafe {NonZeroUsize::new_unchecked(width)}
}
When executed at compile time, it will show the message in the string, but unfortunately the runtime error is unhelpful.
@AaronKutch
But this compiles in Rust 1.33
https://rust.godbolt.org/z/fThezn
use std::num::NonZeroUsize;
// reduced example for my usecase that involves wrappers around `NonZeroUsize`
const fn bw(width: usize) -> NonZeroUsize {
["Tried to construct an invalid BitWidth of 0 using the `apint::bw` function"][(width == 0) as usize];
unsafe {NonZeroUsize::new_unchecked(width)}
}
pub static FOO: NonZeroUsize = bw(30);
Also, replacing the 30
with a 0
errors with index out of bounds as expected.
I should have stated that it probably compiled before the current stable, but I did not know it would compile that long ago.
I noticed that one is required to pass a string literal to core::panic
, needing to use std::panic
to panic with a const S: &str = ....;
, is this going to be fixed soon?
I made a crate for doing compile-time formatting, and find it kind of annoying that the assertc
macro for compile-time assertions won't be usable with #![no_std]
crates once it's released, because of that limitation in core::panic
.
https://github.com/rodrimati1992/const_format_crates/tree/assertions#formatted-const-panics
@rodrimati1992 if you are asking whether it will be possible to use format strings for const panics, the answer is -- not for a long while. Format strings are extremely complicated. The const-eval skill tree show the dependency chain to get panics with formatting to work.
Or maybe you are asking something else (I am confused by your emphasis on core::panic
vs std::panic
), in which case it would help if you had a self-contained example that you think should work.
I am asking about panicking in a #[no_std]
crate with a &'static str
const
ant.
Example:
const POINT: Point = Point{x: 3, y: 5};
const _: () = {
const S: &str = const_format::formatc!("The point is misaligned: {:?}", POINT);
if POINT.x%2==0 { panic!(S) }
};
This is the kind of code that I can write today with std
but not in #![no_std]
,
probably because nobody anticipated that I would make my own formatting machinery that works before const allocations or traits in const fns do.
probably because nobody anticipated that I would make my own formatting machinery that works before const allocations or traits in const fns do.
Yeah, probably.^^ I was not aware this is a thing, impressive.
But, what do you need to make this work no_std
? Again, a self-contained example would help understand the limitation here. Something that doesn't depend on your crate, but demonstrates the problem.
Well, a self contained example would be:
#![feature(const_panic)]
//#![no_std]
use core::mem;
// Let's assume that FOO and BAR come from a dependency
const FOO: (usize, &str) = (mem::size_of::<usize>(), "foo is smaller than bar");
const BAR: (usize, &str) = (mem::size_of::<u32>(), "bar is smaller than foo");
const _: () = {
if FOO.0 < BAR.0 {
panic!(FOO.1)
} else if FOO.0 > BAR.0 {
panic!(BAR.1)
}
};
Uncomment the #![no_std]
line and you'll get errors about how it tries to do something non-const.
This is because the different expansions of panic! in std and core.
In std, panic! is expanded to ::std::rt::begin_panic
, in core it is ::core::panicking::panic_fmt
.
You might want to use core::panicking::panic
instead of panic!.
It's not panic_fmt
though since no format string is used. The problem is ::core::panicking::panic
.
Probably this line needs an exception for that function:
It's not
panic_fmt
though since no format string is used
It is what I get from expanded
: https://rust.godbolt.org/z/rznTzh
Oh right, these are not string literals... I am actually surprised this works in std::panic!
.
Also what is this:
https://github.com/rust-lang/rust/blob/c3364780d2cfddfe329f62a3ec138fd4f9a60e27/library/core/src/macros/mod.rs#L9
I never saw literal
as a macro argument class before.^^
Okay so this works with both the core and std macro:
#![feature(const_panic)]
const FOO:() = std::panic!("hello");
const BAR:() = core::panic!("hello");
AFAIK that is the only part that is intended to work. The fact that std::panic!
works with more arguments is, I think, an accident that might be hard to replicate for core::panic!
... Cc @oli-obk
Oh, this was actually broken by https://github.com/rust-lang/rust/pull/74056. Before that PR, core::panic!
with one argument expanded to ::panicking::panic
and that worked during const-time. It seems we didn't have a test like this though:
#![feature(const_panic)]
const MSG: &str = "hello";
const FOO:() = std::panic!(MSG);
const BAR:() = core::panic!(MSG);
So the regression went unnoticed. That said, it is still unclear to me whether this was ever supposed to work -- and it looks really hard to make this work again after #74056.
Since we're talking about const panics on non-literals; this currently gives an ICE:
#![feature(const_panic)]
const _: () = panic!(123);
Edit: Ah, this was already reported as #66693. Sorry for the noise.
Since we're talking about const panics on non-literals; this currently gives an ICE:
Yup, that's https://github.com/rust-lang/rust/issues/66693.
Okay so this works with both the core and std macro:
#![feature(const_panic)]
const FOO:() = std::panic!("hello");
const BAR:() = core::panic!("hello");
Curious how hard it would be to stabilize this much. It's really all I need for now. I've gone ahead and used the array hack to achieve the same effect, but it'd be nice to have something which provides a reasonable panic message when used in non-const
contexts as well.
@rodrimati1992
I noticed that one is required to pass a string literal to core::panic, needing to use std::panic to panic with a const S: &str = ....;, is this going to be fixed soon?
So after having done the investigation here and at https://github.com/rust-lang/rust/issues/67984#issuecomment-687611718, this is very far from an easy issue to fix. I see no good way to resolve the problem. This is technically regression, but of a nightly-only feature, so probably not a strong enough argument to revert anything, given the arguments in favor of the change that caused the regression.
@tarcieri
One blocking problem is adjusting the const checker to ensure that the argument is indeed a string literal, thus avoiding the ICE. I'm afraid that will break @rodrimati1992's approach; we could still allow that under a separate flag.
Once that's done, I think there was still some discussion around how exactly to print the panic message to the user, but I see no major blockers. I'd prefer to have at least somewhat settled promotion before we stabilize more ways in which CTFE can fail (Cc https://github.com/rust-lang/const-eval/issues/53), but that might be asking too much.
The const non-literal core::panic can be fixed by turning that case also into a lang item. Working demo/patch: https://github.com/rust-lang/rust/compare/master...fusion-engineering-forks:core-const-panic-str
(Not sure if adding a lang item just for this is worth it though. Let me know if you want me to submit this as a PR.)
Edit: Sent as PR now: https://github.com/rust-lang/rust/pull/78069
Ah good point, we can separate that case from the more general panic_fmt
.
The reason I am not using core::panicking::panic
is that It's not even unstably const, so I don't know if it's only intended to be used at compile-time by the core::panic
macro.
@rodrimati1992 that function is an unstable implementation detail that no external crates should use.
I ran into the following weird situation:
The following code only needs #![feature(const_panic)]
:
const A: () = assert!(1 == 1);
const B: () = panic!("Oh noes");
but this code needs #![feature(const_fn)]
and #![feature(const_panic)]
:
const fn foo() {
assert!(1 == 1);
}
const fn bar() {
panic!("hi");
}
This occurs both with core
and std
, but with different error messages.
Is this intentional, or is it related to the https://github.com/rust-lang/rust/pull/74056 issue?
@josephlr Looks like min_const_fn
does not accept panics even with const_panic
enabled. Not sure if that is deliberate -- @oli-obk @ecstatic-morse do you know?
I thought core::panicing::panic
needed to be marked with rustc_const_unstable
, but that didn't work.
The panic functions are non-const. They are subject to a special exception in the const checks, and then during const-evaluation the interpreter notices when these functions are called, and stops interpretation (so their bodies do not even matter).
@RalfJung https://github.com/rust-lang/rust/pull/76602 fixes the issue, thanks for the tips!
Most helpful comment
Anything wrong with just switching the message placement? E.g. from this:
To this:
I don't think there's any ambiguity here. The error is formatted as a compiler-error and the context message explains why it was a compiler error.