Skip to content

Commit 3669e8c

Browse files
authored
Merge pull request #3007 from fusion-engineering-forks/panic
RFC: Plan to make core and std's panic identical.
2 parents d9d459f + 01d4da4 commit 3669e8c

File tree

1 file changed

+187
-0
lines changed

1 file changed

+187
-0
lines changed

text/0000-panic-plan.md

+187
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
- Start Date: 2020-10-25
2+
- RFC PR: [rust-lang/rfcs#3007](https://github.com/rust-lang/rfcs/pull/3007)
3+
- Rust Issue: [#80162](https://github.com/rust-lang/rust/issues/80162)
4+
5+
# Summary
6+
7+
This RFC proposes to make `core::panic!` and `std::panic!` identical and consistent in Rust 2021,
8+
and proposes a way to deal with the differences in earlier editions without breaking code.
9+
10+
# Problems
11+
12+
`core::panic!` and `std::panic!` behave mostly the same, but have their own incompatible quirks for the single-argument case.
13+
14+
This leads to several different problems, which would all be solved if they didn't special-case `panic!(one_argument)`.
15+
16+
For multiple-arguments (e.g. `panic!("error: {}", e)`), both already behave identical.
17+
18+
## Panic
19+
20+
Both do not use `format_args!("..")` for `panic!("..")` like they do for multiple arguments, but use the string literally.
21+
22+
*💔 **Problem 1:** `panic!("error: {}")` is probably a mistake, but compiles fine.*
23+
24+
*💔 **Problem 2:** `panic!("Here's a brace: {{")` outputs two braces (`{{`), not one (`{`).*
25+
26+
In the case of `std::panic!(x)`, `x` does not have to be a string literal, but can be of any (`Any + Send`) type.
27+
This means that `std::panic!("{}")` and even `std::panic!(&"hi")` compile without errors or warnings, even though these are most likely mistakes.
28+
29+
*💔 **Problem 3:** `panic!(123)`, `panic!(&"..")`, `panic!(b"..")`, etc. are probably mistakes, but compile fine with `std`.*
30+
31+
In the case of `core::panic!(x)`, `x` must be a `&str`, but does not have to be a string literal, nor does it have to be `'static`.
32+
This means that `core::panic!("{}")` and `core::panic!(string.as_str())` compile fine.
33+
34+
*💔 **Problem 4:** `let error = String::from("error"); panic!(&error);` works fine in `no_std` code, but no longer compiles when switching `no_std` off.*
35+
36+
*💔 **Problem 5:** `panic!(CustomError::Error);` works with std, but no longer compiles when switching `no_std` on.*
37+
38+
## Assert
39+
40+
`assert!(expr, args..)` and `assert_debug(expr, args..)` expand to `panic!(args..)` and therefore will have all the same problems.
41+
In addition, these can result in confusing mistakes:
42+
43+
```rust
44+
assert!(v.is_empty(), false); // runs panic!(false) if v is not empty 😕
45+
```
46+
47+
*💔 **Problem 6:** `assert!(expr, expr)` should probably have been a `assert_eq!`, but compiles fine and gives no useful panic message.*
48+
49+
Because `core::panic!` and `std::panic!` are different, `assert!` and related macros expand to `panic!(..)`, not to `$crate::panic!(..)`,
50+
making these macros not work with `#![no_implicit_prelude]`, as reported in [#78333](https://github.com/rust-lang/rust/issues/78333).
51+
This also means that the panic of an assert can be accidentally 'hijacked' by a locally defined `panic!` macro.
52+
53+
*💔 **Problem 7:** `assert!` and related macros need to choose between `core::panic!` and `std::panic!`, and can't use `$crate::panic!` for proper hygiene.*
54+
55+
## Implicit formatting arguments
56+
57+
[RFC 2795] adds implicit formatting args, as follows:
58+
59+
```rust
60+
let a = 4;
61+
println!("a is {a}");
62+
```
63+
64+
It modifies `format_args!()` to automatically capture variables that are named in a formatting placeholder.
65+
66+
With the current implementations of `panic!()` (both core's and std's), this would not work if there are no additional explicit arguments:
67+
68+
```rust
69+
let a = 4;
70+
71+
println!("{}", a); // prints `4`
72+
panic!("{}", a); // panics with `4`
73+
74+
println!("{a}"); // prints `4`
75+
panic!("{a}"); // panics with `{a}` 😕
76+
77+
println!("{a} {}", 4); // prints `4 4`
78+
panic!("{a} {}", 4); // panics with `4 4`
79+
```
80+
81+
*💔 **Problem 8:** `panic!("error: {error}")` will silently not work as expected, after [RFC 2795] is implemented.*
82+
83+
## Bloat
84+
85+
`core::panic!("hello {")` produces the same `fmt::Arguments` as `format_args!("hello {{")`, not `format_args!("{}", "hello {")` to avoid pulling in string's `Display` code,
86+
which can be quite big.
87+
88+
However, `core::panic!(non_static_str)` does need to expand to `format_args!("{}", non_static_str)`, because `fmt::Arguments` requires a `'static` lifetime
89+
for the non-formatted pieces. Because the `panic!` `macro_rules` macro can't distinguish between non-`'static` and `'static` values,
90+
this optimization is only applied to what macro_rules consider a `$_:literal`, which does not include `concat!(..)` or `CONST_STR`.
91+
92+
*💔 **Problem 9:** `const CONST_STR: &'static str = "hi"; core::panic!(CONST_STR)` works,
93+
but will silently result in a lot more generated code than `core::panic!("hi")`.
94+
(And also needs [special handling](https://github.com/rust-lang/rust/pull/78069) to make `const_panic` work.)*
95+
96+
# Solution if we could go back in time
97+
98+
None of these these problems would have existed if
99+
1\) `panic!()` did not handle the single-argument case differently, and
100+
2\) `std::panic!` was no different than `core::panic!`:
101+
102+
```rust
103+
// core
104+
macro_rules! panic {
105+
() => (
106+
$crate::panic!("explicit panic")
107+
);
108+
($($t:tt)*) => (
109+
$crate::panicking::panic_fmt($crate::format_args!($($t)+))
110+
);
111+
}
112+
113+
// std
114+
use core::panic;
115+
```
116+
117+
The examples from problems 1, 2, 3, 4, 5, 6 and 9 would simply not compile, and problems 7 and 8 would not occur.
118+
119+
However, that would break too much existing code.
120+
121+
# Proposed solution
122+
123+
Considering we should not break existing code, I propose we gate the breaking changes on the 2021 edition.
124+
125+
In addition, we add a lint that *warns* about the problems in Rust 2015/2018, while not giving errors or changing the behaviour.
126+
127+
Specifically:
128+
129+
- Only for Rust 2021, we apply the breaking changes as in the previous section.
130+
So, `core::panic!` and `std::panic!` are the same, and *always* put their arguments through `format_args!()`.
131+
132+
Any optimization that needs special casing should be done *after* `format_args!()`.
133+
(E.g. using [`fmt::Arguments::as_str()`](https://github.com/rust-lang/rust/pull/74056),
134+
as is [already done](https://github.com/rust-lang/rust/pull/78119) for `core::panic!("literal")`.)
135+
136+
This means `std::panic!(x)` can no longer be used to panic with arbitrary (`Any + Send`) payloads.
137+
138+
- We [add `std::panic::panic_any(x)`](https://github.com/rust-lang/rust/pull/74622),
139+
that still allows programs with std to panic with arbitrary (`Any + Send`) payloads.
140+
141+
- We [add a lint](https://github.com/rust-lang/rust/pull/78088) for Rust 2015/2018 that warns about problem 1, 2, and 8,
142+
similar to [what Clippy already has](https://rust-lang.github.io/rust-clippy/master/index.html#panic_params).
143+
144+
Note that this lint isn't just to warn about incompatibilities with Rust 2021, but also to warn about usages of `panic!()` that are likely mistakes.
145+
146+
This lint suggests add an argument to `panic!("hello: {}")`, or to insert `"{}", ` to use it literally: `panic!("{}", "hello: {}")`.
147+
([Screenshot here.](https://user-images.githubusercontent.com/783247/96643867-79eb1080-1328-11eb-8d4e-a5586837c70a.png))
148+
The second suggestion can be a pessimization for code size, but I believe that [can be solved separately](https://github.com/rust-lang/rust/issues/78356).
149+
150+
- After `panic_any` is stable, we add a lint for Rust 2015/2018 (or extend the one above) to warn about problem 3, 4, 5 and 9.
151+
It warns about `panic!(x)` for anything other than a string literal, and suggests to use
152+
`panic_any(x)` instead of `std::panic!(x)`, and
153+
`panic!("{}", x)` instead of `core::panic!(x)`.
154+
155+
It will also detect problem 6 (e.g. `assert!(true, false)`) because that expands to such a panic invocation,
156+
but will suggest `assert_eq!()` for this case instead.
157+
158+
- We [modify the panic glue between core and std](https://github.com/rust-lang/rust/pull/78119)
159+
to use `Arguments::as_str()` to make sure both `std::panic!("literal")` and `core::panic!("literal")`
160+
result in a `&'static str` payload. This removes one of the differences between the two macros in Rust 2015/2018.
161+
162+
This is already merged.
163+
164+
- Now that `std::panic!("literal")` and `core::panic!("literal")` behave identically,
165+
[we modify `todo!()`, `unimplemented!()`, `assert_eq!()`, etc.](https://github.com/rust-lang/rust/pull/78343)
166+
to use `$crate::panic!()` instead of `panic!()`. This solves problem 7 for all macros except `assert!()`.
167+
168+
- We modify `assert!()` to use `$crate::panic!()` instead of `panic!()` for the single argument case in Rust 2015/2018,
169+
and for all cases in Rust 2021.
170+
171+
This solves problem 7 for the common case of `assert!(expr)` in Rust 2015/2018, and for all cases of `assert!` in Rust 2021.
172+
173+
Together, these actions address all problems, without breaking any existing code.
174+
175+
# Drawbacks
176+
177+
- This results in subtle differences between Rust editions.
178+
179+
- This requires `assert!` and `panic!` to behave differently depending on the Rust edition of the crate it is used in.
180+
`panic!` is just a `macro_rules` macro right now, which does not natively support that.
181+
182+
# Alternatives
183+
184+
- Instead of the last step, we could also simply break `assert!(expr, non_string_literal)` in all editions.
185+
This usage is probably way less common than `panic!(non_string_literal)`.
186+
187+
[RFC 2795]: https://rust-lang.github.io/rfcs/2795-format-args-implicit-identifiers.html

0 commit comments

Comments
 (0)