-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make bool
an enum
#348
Comments
Document no_std support
Whatever happened to this? =P (is it still backwards compatible?) |
We document: https://doc.rust-lang.org/1.34.0/std/primitive.bool.html
Casting here likely refers to the However as far as I can tell we currently make no promise about the memory representation of If |
Wouldn't the casting behaviour and guaranteed size_of still be possible with #[repr(u8)]
enum bool {
false = 0,
true = 1,
} |
The problem with representing |
@gorilskij I don't see how this is a big issue. Obviously we have to preserve |
@KrishnaSannasi I would tend to agree, both for compatibility and because There is another, more serious point. As proven by the fact that As an example to illustrate what I mean, Haskell implements TL;DR I think making |
I would argue that a Also a #[repr(u8)]
enum bool {
false = 0,
true = 1
} and then special casing Note that there is movement to make One more advantage to this is that it reduces the number of keywords in Rust, because edit: |
Perhaps you're right. I still think that in an ideal world it would be |
Well, if $cond {
$block
} gets desugared to match $cond {
bool::true => $block,
bool::false => (),
} And similarly for
I don't think an edition can do this, it seems like to big of a breaking change. |
Alternately, edit: Actually, that's a bad idea because it conflicts with the proposal to make |
I understand that |
if, while, for and co. are already desugared to loop + match. |
“Language items” are not dirty, they are often necessary. For example I think there is no real blocker to making |
I don't see any benefits other than "it looks cleaner" which I'd argue it doesn't due to, at least, the naming convention inconsistency. |
No-one is suggesting we change the name. Using an enum instead of a bool would simplify some special-casing in the compiler, which would be an advantage. |
I was talking about the issue purely from a user's point of view, I have no knowledge of compiler internals (or experience in this area for that matter). Just out of curiosity, how would switching to an enum representation help? It seems to me that a primitive type would be easier to write special cases for. (btw, if this is not the place for such discussion, tell me, I'll move it somewhere else) |
@gorilskij as @varkor said it would reduce special casing the compiler, so it would make the compiler simpler. Special casing only makes things more complex, not the other way around. For some things the complexity is justified, but with |
Oh, sorry, I misread it as "it would make it simpler to implement special casing." Thanks for clarifying. |
@varkor What's the way forward on this? It seems like it's a good idea, given that it would reduce the special-casing in the compiler. |
@jhpratt: there was a brief discussion on Discord about it a while ago. You'd probably want to talk to @oli-obk or @eddyb about it if you wanted to pursue it, who both had some ideas about it. |
After a slight bit of discussion on Reddit, a couple things came up. Would the I'll (finally) reach out on Discord to them to see if they have any ideas. |
We can keep the keywords and have them refer to the variants of the enum. There might be some diagnostics changes to work out, but I don't think it'd be a problem. |
I think it's mostly compiler-internal work that is blocking this. I tried it once and it's not super simple to do. Loads of code expects to know that something is a bool or make something a bool, and for this we right now have a very convenient way ( |
I have doubts that this "reduce special casing the compiler", |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
I think that 2014 or earlier would have been a good time for a change like this. At this point doing it without breaking things would take non-trivial care while the benefits as far as I can tell are entirely theoretical. It would feel nice to have fewer special cases in the language, but is there any concrete benefit to users? I think that formally deciding not to make this change (as opposed to it being merely postponed) should be an option on the table for @rust-lang/lang. |
I would personally like to avoid considering whether |
@SimonSapin What's the harm in leaving this open, in case someone wanted to at least try it to see the admittedly uncertain benefits? That's what I was going to do, but @Centril said it would likely conflict with other ongoing work. |
It sounds like you answered your own question? |
If this were a formal RFC submitted now, what should the Motivation section contain? From the template:
|
The conflict with ongoing work would be temporary (iirc he said if-let chains), as the bits it would touch would eventually be completed. My interpretation of your original comment was a permanent (or at least indefinite) declination of the (pseudo-)RFC. |
This is a compiler implementation detail and doesn't affect the language. The only possible user-facing change this could have is changing diagnostics, but these are easily special-cased anyway. While I don't think this ought to be a priority, if someone wants to try changing it to see whether the consequences for code clarity are advantageous, there's no reason to dissuade them (other than their time perhaps being better placed with more useful features). |
why not use just 1 bit for |
If that were the case, then you wouldn't be able to safely take a reference to the boolean. Note that the section you are referring to is titled packed struct. |
https://ziglang.org/documentation/0.9.1/#boolToInt @boolToInt(value: bool) u1 Converts true to If the value is known at compile-time, the return type is |
If you are responding to me, it’s not clear what the argument/point of the response is. You asked why a Boolean can’t (universally?) be represented as a single bit. The answer is that you can’t take a reference to a bit (memory addresses only have byte granularity) and values in Rust can generally be referenced. You’ll need to clarify what point your response is trying to make. |
For what it's worth rust actually does use the |
I have presented too much Rust fandom in the past. Time for some hate! (I still love Rust, but I need an archive for the things I am sad about it) (Title is inspired by http://phpsadness.com/) (Some of these are unpopular opinions, quite many people think differently) ## Standard library ### Primitives #### Primitives are not really primitive [Why is `bool` not an `enum Bool { False, True }`?](rust-lang/rfcs#348) [Why is `str` not a `struct Str([u8]);`?](rust-lang/rfcs#2692) #### Conversion between integers is not explicit `as` conversions do not explicitly state whether and what is lost. For example, `u8 as u16` is perfectly fine (expand size), but `u16 as u8` is a truncation, `u16 as i16` might overflow, `u16 as f32` is perfectly fine (lossless), `u32 as f32` is lossy, `usize as u32` and `u64 as usize` are lossy depending on the platform... On the other hand, while we can use `.try_into()` on the numbers, it is unclear what the impacts are, and handling the error is more challenging if panic is not desired. It is difficult to find (if it even exists) the *named* safe conversion method from the standard library documentation. To solve this problem, I published the [xias](https://docs.rs/xias) crate, which exposes a blanket impl on primitive types to facilitate explicitly explained conversions. ### Collections #### `.len()` and `.is_empty()` Clippy [recommends](https://rust-lang.github.io/rust-clippy/master/index.html#len_without_is_empty) that types implementing `.len()` should also implement `.is_empty()`. This much sounds like `.clone()` and `.clone_from()`, which should use a default trait impl. Why wasn't `.len()` a trait anyway? ## General syntax ### Operators #### The `!` operator ```rs if !matches!(value, Enum::Variant) { panic!("Do you think value matches Enum::Variant or not here, \ if you didn't know Rust?"); } ``` An alternative is to use `condition.not()`, but that requires `use std::ops::Not` everywhere. What about `condition == false`? [Thanks so much clippy.](https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison) #### `.await` No, don't get me wrong, I think `future.await` is much better than `await future`. Not a fan of cargo cult. But `future.await` looks as if it is a field, especially if the syntax highlighter is not aware of it (e.g. in outdated syntax highlighting libraries). Why can't we make it *look like* a postfix macro, like `future.await!` or `future.await!()`? ### Pattern matching #### `let` has mixed use in assignment and conditions If you didn't know Rust, you're probably confused why we use `=` instead of `==` here ```rs if let Enum::Variant = value { // ... } ``` This is getting even more confusing with the let...else syntax ```rs let Ok(socket) = Socket::bind(addr) else { anyhow::bail!("Cannot bind socket to {addr}"); }; ``` Guess what, `expr else { ... }` itself is not a condition. You actually group it as `let pat = expr` `else` `{...}` instead. #### Ambiguous new bindings ```rs pub fn validate(input_byte: u8) -> u8 { const FORBIDDEN: u8 = 42; match input_byte { FORBIDDEN => panic!("input byte cannot be {FORBIDDEN}"), value => return value, } } fn main() { dbg!(validate(41)); dbg!(validate(42)); } ``` ``` [src/main.rs:11] validate(41) = 41 thread 'main' panicked at 'input byte cannot be 42', src/main.rs:5:22 ``` `FORBIDDEN` is a constant but `value` is a new binding? how is this even supposed to work? #### Use of `|` in patterns ```rs fn convert_bitmask(input: u8) -> Option<bool> { match x { Some(1 | 2 | 4) => Some(true), Some(..8) => Some(false), _ => None, } } ``` Didn't learn Rust before? Guess what the above means: - "returns true if x is 1 or 2 or 4" - "returns true if x is 7 (1 bitwise-OR 2 bitwise-OR 4)" #### `_` and `_var` mean different things You may have noticed that you can suppress the unused variable warning with both `let _ =` and `let _var =`. Golang prefers the former (the latter doesn't work in Go), but they actually mean different things in Rust. `_var` is just a normal binding name (local variable name in the case of `let _var =`), similar to `var`, except it suppresses some warnings about unused identifiers. `_` is the pattern that matches everything and binds the value to nothing. Since there is no binding, the value is dropped immediately. This causes a significant difference when it comes to RAII: ```rs { let _guard = mutex.lock(); println!("mutex is still locked here"); } println!("mutex is unlocked here because `_guard` is dropped"); ``` ```rs { let _ = mutex.lock(); println!("mutex is already unlocked here because `_` drops immediately"); } ``` Fortunately, this is usually not a problem, because it is rare to acquire an unused RAII object (although it is still used in some rare occasions). ## Testing ### Difficulty to unit test procedural macros Procedural macros are typically tested in the form of integration tests, where the user writes example code to use the procedural macro and check if it compiles correctly. However, this means the whole program is tested together, and breaking any single part would cause all tests to fail. This is particularly troublesome when errors caused by macro output are hard to debug. It doesn't make sense to unit test particular functions that generate `TokenStream` output, because that would imply writing test cases that repeat every token in the generated output, and modifying or even reformatting (e.g. adding trailing commas) any part of the output would require updating all unit tests. We want to unit test the logic to ensure that each unit contributes what it is expected to contribute, not to unit test the exact shape of the generated code. Another option is to have `#[cfg(test)] #[proc_macro*] pub fn` macros that expose the internal functions, but this suffers from two problems. Firstly, this involves additional code to parse an additional TokenStream to serve as the input arguments, and sometimes inapplicable when the macro generates a special, incomplete part of the syntax tree like match arms, struct fields, trait bounds, other non-item non-statement ASTs, or a combination of multiple ASTs (e.g. multiple non-exhaustive match arms). Secondly, this only works when the integration test is located in the same package as the procedural macro. This is often not the case when the procedural macro references some code in another closely coupled crate, e.g. `derive@serde_derive::Deserialize` depends on `trait@serde::Deserialize`. This can be solved by setting a dependent as a dev-dependency, but such a solution would not be scalable. ### Inflexible panic tests Unit tests currently allow `#[should_panic]` to assert that a unit test should panic. `#[should_panic(expected = "substring")]` additionally asserts that the panic message must contain `substring`. This is not ideal for two reasons. Firstly, substrings may not be safe as some parts of the panic message remain untested. Secondly, the panic message is an implementation detail of the panic but not the subject to be tested. The goal is to test that a certain code panics for a known reason, not to test what it shows to the user. For example, `panic!("Cannot insert entry into string map because {:?} is not one of String or &'static str", ty)`, where `ty: std::any::TypeId`, cannot be tested properly, because we cannot make sure that both `insert entry into the string map` and `is not one of String or &'static str` appear in the panic message since `TypeId as fmt::Debug` has inconsistent output (which is perfectly fine because it should not be relied on). This also means we cannot test which `TypeId` the panic involves (although a crate like this should probably attempt to take the `any::type_name` for slightly more consistent output). Coauthored-by: xqwtxon <xqwtxon@hotmail.com>
This wiki records some of my unpopular opinions. Since this wiki is expected to be somewhat unorganized, there is no index page. See the sidebar if you want to continue browsing. I have presented too much Rust fandom in the past. Time for some hate! (I still love Rust, but I need an archive for the things I am sad about it) (Title is inspired by http://phpsadness.com/) (Some of these are unpopular opinions, quite many people think differently) ## Standard library ### Primitives #### Primitives are not really primitive [Why is `bool` not an `enum Bool { False, True }`?](rust-lang/rfcs#348) [Why is `str` not a `struct Str([u8]);`?](rust-lang/rfcs#2692) #### Conversion between integers is not explicit `as` conversions do not explicitly state whether and what is lost. For example, `u8 as u16` is perfectly fine (expand size), but `u16 as u8` is a truncation, `u16 as i16` might overflow, `u16 as f32` is perfectly fine (lossless), `u32 as f32` is lossy, `usize as u32` and `u64 as usize` are lossy depending on the platform... On the other hand, while we can use `.try_into()` on the numbers, it is unclear what the impacts are, and handling the error is more challenging if panic is not desired. It is difficult to find (if it even exists) the *named* safe conversion method from the standard library documentation. To solve this problem, I published the [xias](https://docs.rs/xias) crate, which exposes a blanket impl on primitive types to facilitate explicitly explained conversions. ### Collections #### `.len()` and `.is_empty()` Clippy [recommends](https://rust-lang.github.io/rust-clippy/master/index.html#len_without_is_empty) that types implementing `.len()` should also implement `.is_empty()`. This much sounds like `.clone()` and `.clone_from()`, which should use a default trait impl. Why wasn't `.len()` a trait anyway? ## General syntax ### Operators #### The `!` operator ```rs if !matches!(value, Enum::Variant) { panic!("Do you think value matches Enum::Variant or not here, \ if you didn't know Rust?"); } ``` An alternative is to use `condition.not()`, but that requires `use std::ops::Not` everywhere. What about `condition == false`? [Thanks so much clippy.](https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison) #### `.await` No, don't get me wrong, I think `future.await` is much better than `await future`. Not a fan of cargo cult. But `future.await` looks as if it is a field, especially if the syntax highlighter is not aware of it (e.g. in outdated syntax highlighting libraries). Why can't we make it *look like* a postfix macro, like `future.await!` or `future.await!()`? ### Pattern matching #### `let` has mixed use in assignment and conditions If you didn't know Rust, you're probably confused why we use `=` instead of `==` here ```rs if let Enum::Variant = value { // ... } ``` This is getting even more confusing with the let...else syntax ```rs let Ok(socket) = Socket::bind(addr) else { anyhow::bail!("Cannot bind socket to {addr}"); }; ``` Guess what, `expr else { ... }` itself is not a condition. You actually group it as `let pat = expr` `else` `{...}` instead. #### Ambiguous new bindings ```rs pub fn validate(input_byte: u8) -> u8 { const FORBIDDEN: u8 = 42; match input_byte { FORBIDDEN => panic!("input byte cannot be {FORBIDDEN}"), value => return value, } } fn main() { dbg!(validate(41)); dbg!(validate(42)); } ``` ``` [src/main.rs:11] validate(41) = 41 thread 'main' panicked at 'input byte cannot be 42', src/main.rs:5:22 ``` `FORBIDDEN` is a constant but `value` is a new binding? how is this even supposed to work? #### Use of `|` in patterns ```rs fn convert_bitmask(input: u8) -> Option<bool> { match x { Some(1 | 2 | 4) => Some(true), Some(..8) => Some(false), _ => None, } } ``` Didn't learn Rust before? Guess what the above means: - "returns true if x is 1 or 2 or 4" - "returns true if x is 7 (1 bitwise-OR 2 bitwise-OR 4)" #### `_` and `_var` mean different things You may have noticed that you can suppress the unused variable warning with both `let _ =` and `let _var =`. Golang prefers the former (the latter doesn't work in Go), but they actually mean different things in Rust. `_var` is just a normal binding name (local variable name in the case of `let _var =`), similar to `var`, except it suppresses some warnings about unused identifiers. `_` is the pattern that matches everything and binds the value to nothing. Since there is no binding, the value is dropped immediately. This causes a significant difference when it comes to RAII: ```rs { let _guard = mutex.lock(); println!("mutex is still locked here"); } println!("mutex is unlocked here because `_guard` is dropped"); ``` ```rs { let _ = mutex.lock(); println!("mutex is already unlocked here because `_` drops immediately"); } ``` Fortunately, this is usually not a problem, because it is rare to acquire an unused RAII object (although it is still used in some rare occasions). ## Testing ### Difficulty to unit test procedural macros Procedural macros are typically tested in the form of integration tests, where the user writes example code to use the procedural macro and check if it compiles correctly. However, this means the whole program is tested together, and breaking any single part would cause all tests to fail. This is particularly troublesome when errors caused by macro output are hard to debug. It doesn't make sense to unit test particular functions that generate `TokenStream` output, because that would imply writing test cases that repeat every token in the generated output, and modifying or even reformatting (e.g. adding trailing commas) any part of the output would require updating all unit tests. We want to unit test the logic to ensure that each unit contributes what it is expected to contribute, not to unit test the exact shape of the generated code. Another option is to have `#[cfg(test)] #[proc_macro*] pub fn` macros that expose the internal functions, but this suffers from two problems. Firstly, this involves additional code to parse an additional TokenStream to serve as the input arguments, and sometimes inapplicable when the macro generates a special, incomplete part of the syntax tree like match arms, struct fields, trait bounds, other non-item non-statement ASTs, or a combination of multiple ASTs (e.g. multiple non-exhaustive match arms). Secondly, this only works when the integration test is located in the same package as the procedural macro. This is often not the case when the procedural macro references some code in another closely coupled crate, e.g. `derive@serde_derive::Deserialize` depends on `trait@serde::Deserialize`. This can be solved by setting a dependent as a dev-dependency, but such a solution would not be scalable. ### Inflexible panic tests Unit tests currently allow `#[should_panic]` to assert that a unit test should panic. `#[should_panic(expected = "substring")]` additionally asserts that the panic message must contain `substring`. This is not ideal for two reasons. Firstly, substrings may not be safe as some parts of the panic message remain untested. Secondly, the panic message is an implementation detail of the panic but not the subject to be tested. The goal is to test that a certain code panics for a known reason, not to test what it shows to the user. For example, `panic!("Cannot insert entry into string map because {:?} is not one of String or &'static str", ty)`, where `ty: std::any::TypeId`, cannot be tested properly, because we cannot make sure that both `insert entry into the string map` and `is not one of String or &'static str` appear in the panic message since `TypeId as fmt::Debug` has inconsistent output (which is perfectly fine because it should not be relied on). This also means we cannot test which `TypeId` the panic involves (although a crate like this should probably attempt to take the `any::type_name` for slightly more consistent output). Co-authored-by: xqwtxon <xqwtxon@hotmail.com>
This wiki records some of my unpopular opinions. Since this wiki is expected to be somewhat unorganized, there is no index page. See the sidebar if you want to continue browsing. I have presented too much Rust fandom in the past. Time for some hate! (I still love Rust, but I need an archive for the things I am sad about it) (Title is inspired by http://phpsadness.com/) (Some of these are unpopular opinions, quite many people think differently) ## Standard library ### Primitives #### Primitives are not really primitive [Why is `bool` not an `enum Bool { False, True }`?](rust-lang/rfcs#348) [Why is `str` not a `struct Str([u8]);`?](rust-lang/rfcs#2692) #### Conversion between integers is not explicit `as` conversions do not explicitly state whether and what is lost. For example, `u8 as u16` is perfectly fine (expand size), but `u16 as u8` is a truncation, `u16 as i16` might overflow, `u16 as f32` is perfectly fine (lossless), `u32 as f32` is lossy, `usize as u32` and `u64 as usize` are lossy depending on the platform... On the other hand, while we can use `.try_into()` on the numbers, it is unclear what the impacts are, and handling the error is more challenging if panic is not desired. It is difficult to find (if it even exists) the *named* safe conversion method from the standard library documentation. To solve this problem, I published the [xias](https://docs.rs/xias) crate, which exposes a blanket impl on primitive types to facilitate explicitly explained conversions. ### Collections #### `.len()` and `.is_empty()` Clippy [recommends](https://rust-lang.github.io/rust-clippy/master/index.html#len_without_is_empty) that types implementing `.len()` should also implement `.is_empty()`. This much sounds like `.clone()` and `.clone_from()`, which should use a default trait impl. Why wasn't `.len()` a trait anyway? ## General syntax ### Operators #### The `!` operator ```rs if !matches!(value, Enum::Variant) { panic!("Do you think value matches Enum::Variant or not here, \ if you didn't know Rust?"); } ``` An alternative is to use `condition.not()`, but that requires `use std::ops::Not` everywhere. What about `condition == false`? [Thanks so much clippy.](https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison) #### `.await` No, don't get me wrong, I think `future.await` is much better than `await future`. Not a fan of cargo cult. But `future.await` looks as if it is a field, especially if the syntax highlighter is not aware of it (e.g. in outdated syntax highlighting libraries). Why can't we make it *look like* a postfix macro, like `future.await!` or `future.await!()`? ### Pattern matching #### `let` has mixed use in assignment and conditions If you didn't know Rust, you're probably confused why we use `=` instead of `==` here ```rs if let Enum::Variant = value { // ... } ``` This is getting even more confusing with the let...else syntax ```rs let Ok(socket) = Socket::bind(addr) else { anyhow::bail!("Cannot bind socket to {addr}"); }; ``` Guess what, `expr else { ... }` itself is not a condition. You actually group it as `let pat = expr` `else` `{...}` instead. #### Ambiguous new bindings ```rs pub fn validate(input_byte: u8) -> u8 { const FORBIDDEN: u8 = 42; match input_byte { FORBIDDEN => panic!("input byte cannot be {FORBIDDEN}"), value => return value, } } fn main() { dbg!(validate(41)); dbg!(validate(42)); } ``` ``` [src/main.rs:11] validate(41) = 41 thread 'main' panicked at 'input byte cannot be 42', src/main.rs:5:22 ``` `FORBIDDEN` is a constant but `value` is a new binding? how is this even supposed to work? #### Use of `|` in patterns ```rs fn convert_bitmask(input: u8) -> Option<bool> { match x { Some(1 | 2 | 4) => Some(true), Some(..8) => Some(false), _ => None, } } ``` Didn't learn Rust before? Guess what the above means: - "returns true if x is 1 or 2 or 4" - "returns true if x is 7 (1 bitwise-OR 2 bitwise-OR 4)" #### `_` and `_var` mean different things You may have noticed that you can suppress the unused variable warning with both `let _ =` and `let _var =`. Golang prefers the former (the latter doesn't work in Go), but they actually mean different things in Rust. `_var` is just a normal binding name (local variable name in the case of `let _var =`), similar to `var`, except it suppresses some warnings about unused identifiers. `_` is the pattern that matches everything and binds the value to nothing. Since there is no binding, the value is dropped immediately. This causes a significant difference when it comes to RAII: ```rs { let _guard = mutex.lock(); println!("mutex is still locked here"); } println!("mutex is unlocked here because `_guard` is dropped"); ``` ```rs { let _ = mutex.lock(); println!("mutex is already unlocked here because `_` drops immediately"); } ``` Fortunately, this is usually not a problem, because it is rare to acquire an unused RAII object (although it is still used in some rare occasions). ## Testing ### Difficulty to unit test procedural macros Procedural macros are typically tested in the form of integration tests, where the user writes example code to use the procedural macro and check if it compiles correctly. However, this means the whole program is tested together, and breaking any single part would cause all tests to fail. This is particularly troublesome when errors caused by macro output are hard to debug. It doesn't make sense to unit test particular functions that generate `TokenStream` output, because that would imply writing test cases that repeat every token in the generated output, and modifying or even reformatting (e.g. adding trailing commas) any part of the output would require updating all unit tests. We want to unit test the logic to ensure that each unit contributes what it is expected to contribute, not to unit test the exact shape of the generated code. Another option is to have `#[cfg(test)] #[proc_macro*] pub fn` macros that expose the internal functions, but this suffers from two problems. Firstly, this involves additional code to parse an additional TokenStream to serve as the input arguments, and sometimes inapplicable when the macro generates a special, incomplete part of the syntax tree like match arms, struct fields, trait bounds, other non-item non-statement ASTs, or a combination of multiple ASTs (e.g. multiple non-exhaustive match arms). Secondly, this only works when the integration test is located in the same package as the procedural macro. This is often not the case when the procedural macro references some code in another closely coupled crate, e.g. `derive@serde_derive::Deserialize` depends on `trait@serde::Deserialize`. This can be solved by setting a dependent as a dev-dependency, but such a solution would not be scalable. ### Inflexible panic tests Unit tests currently allow `#[should_panic]` to assert that a unit test should panic. `#[should_panic(expected = "substring")]` additionally asserts that the panic message must contain `substring`. This is not ideal for two reasons. Firstly, substrings may not be safe as some parts of the panic message remain untested. Secondly, the panic message is an implementation detail of the panic but not the subject to be tested. The goal is to test that a certain code panics for a known reason, not to test what it shows to the user. For example, `panic!("Cannot insert entry into string map because {:?} is not one of String or &'static str", ty)`, where `ty: std::any::TypeId`, cannot be tested properly, because we cannot make sure that both `insert entry into the string map` and `is not one of String or &'static str` appear in the panic message since `TypeId as fmt::Debug` has inconsistent output (which is perfectly fine because it should not be relied on). This also means we cannot test which `TypeId` the panic involves (although a crate like this should probably attempt to take the `any::type_name` for slightly more consistent output). Co-authored-by: xqwtxon <xqwtxon@hotmail.com>
Currently,
bool
is not anenum
, but there's no strong reason for that state of affairs. Making it anenum
would increase overall consistency in the language.This is a backwards-compatible change (depending on the specifics) and has been postponed till after 1.0.
See #330.
The text was updated successfully, but these errors were encountered: