-
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
Add generic_assert #2011
Add generic_assert #2011
Conversation
Thoughts: We can't fail to compile if intermediate portions of the expression can't be stringified as people are already potentially using assert on things which can be compared but not stringified. This would be a breaking change. If Rust asserts are checked in release builds, the performance hit is a problem. I'm not sure if they are or not. This has yet to come up for me in a context where it matters. The performance hit can be handled by providing a way to enable or disable the extended format, then branching on it. I think that just printing both sides of the comparison operator would be sufficient for most use cases. This also comes with less of a performance hit. What about multi-statement expressions in braces? Is this currently allowed in assert? The reference says that they do count as expressions, so I don't see why not. The splitting algorithm here doesn't account for it. While I doubt many users are using it, it should be considered. I could see someone doing it somehow inside a deep macro, however. |
@camlorn |
What performance hit? The RFC only changes what is printed on assertion failure. If your thread is crashing due to a logic error, you hardly care about the number of microseconds it takes to print out the error message. But as mentioned already, changing what's allowed in assert is not realistically possible. I'd suggest to simplify this to special handling when the expression is a comparison (PartialEq, PartialOrd) of values that implement Debug. Anything more is just a massive waste of time and complexity. |
In order to print the expression's intermediate results, the assert macro has to save them all and then drop them all immediately after the assert succeeds. Since it's doing things with them in the failure case, LLVM or similar is not going to be able to eliminate temporaries and optimization passes aren't going to kill the cost. This is mentioned in the drawbacks section. |
I don't agree that's a concern for any real code. If the code's performance is so sensitive, you're going to use debug_assert anyway. |
I didn't come up with it, but I do think it's a concern. It might be worth profiling. My thought is that on X86 current asserts are probably close to free because the branch predictor will just predict correctly save in the case of bugs. My other concerns are probably more of a problem: there does appear to be a compatibility issue here as well. I could be wrong or misunderstanding the RFC. |
I do agree with those other concerns. I also agree with the suggestion to simplify the RFC down to just comparison operators, and stick with current behavior when that doesn't apply. That would solve the major motivation (getting rid of assert_eq), without much code bloat. |
What if the special debugging code was present for debug and test builds, but assert had it's present behavior in release builds? (Or have a compiler flag to determine the assert format that is configured to the old style in release styles by default). |
text/0000-generic-assert.md
Outdated
[unresolved]: #unresolved-questions | ||
|
||
## Error messages | ||
- Should we use the negated version of operators (e.g. `!=` for eq) to explain the failure? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably not. The negated operator may not be true for order operations if the type only implements PartialOrd
. For example, f64::NaN < 5
and f64::NaN >= 5
are both false.
I agree, we should have some fallback if intermediate results don't implement |
You would also need a fallback if intermediate results don't implement #[derive(Debug, PartialEq)]
struct Foo(u32);
impl ::std::ops::Add for Foo {
type Output = Self;
fn add(self, rhs: Self) -> Self { Foo(self.0 + rhs.0) }
}
pub fn main() {
let (x, y, z) = (Foo(1), Foo(2), Foo(3));
assert!(x == y + z)
} According to the RFC as written this should show the error message:
but the only way to do so is to prepare that message before executing the expression as Because of this issue I'm also in favor of limiting this to handling |
I think we should limit the amount of code included in release builds. For debug and test builds I'm all for having beautiful and informative output. In these (developer build) cases I don't mind the extra costs. |
To address the issues of message generation, I propose to make there a default in the case that:
However, I still found boolean-related operators important; if we only did the magic for comparison operators, the message will become useless when you used Regarding the performance regression, let me explain in detail, using
This takes up extra stack space a/o registers. If the amount of usable registers are reduced, this may slightly degrade the performance. I still support the point that assert is not free, and I think such things are acceptable. |
This is cool, but I'd prefer it only for |
@pornel The overhead is minimal, as it just takes a little more stack/register space, or only overhead on failure. |
I think this is most useful in |
Perhaps it can prevent inlining or other optimizations by bloating code. |
Ping @dtolnay, can you help drive this to a conclusion? |
I am totally on board with better output from the assert macro. This RFC is not introducing any new API or really any irreversible commitment at all, so I don't think it is productive to pin things down any more than is already written. The things we would be agreeing to here are:
@rfcbot fcp merge |
Team member @dtolnay has proposed to merge this. The next step is review by the rest of the tagged teams: No concerns currently listed. Once these reviewers reach consensus, this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
I find the "detailed design" not detailed enough.
|
I will update the examples according to the concerns soon. Omitting function call parameters is an unfortunate thing, but I consider it should be formatted with a user provided rule. |
@ishitatsuyuki Accessing a field and accessing a method is different though. There is no member in |
I'm going to check my checkbox, but it's mostly based on what @dtolnay explained above. I find the exact detailed design here, like @kennytm, pretty lacking in how it would happen or also missing crucial details like the point about release builds probably don't want such special treatment. I think it's a totally fine idea to move I'm not really sure the text of the RFC is reflecting the thrust of the FCP, but @ishitatsuyuki would you be interested in updating it along the lines of what @dtolnay commented? |
I have updated the text. Feel free to point out when I'm wrong. |
text/0000-generic-assert.md
Outdated
|
||
On assertion failure, the expression itself is stringified, and another line with intermediate values are printed out. The values should be printed with `Debug`, and a plain text fallback if the following conditions fail: | ||
- the type doesn't implement `Debug`. | ||
- the operator is non-comparison (those in `std::ops`) and the type (may also be a reference) doesn't implement `Copy`. | ||
|
||
To make sure that there's no side effects involved (e.g. running `next()` twice on `Iterator`), each value should be stored as temporaries and dumped on assertion failure. | ||
|
||
The new assert messages are likely to generate longer code, and it may be simplified for release builds (if benchmarks confirm the slowdown). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's really no reason to include this functionality in release builds at all. You don't want the increased code size, nor this information, in released software.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of Rust's advantage is that you can get backtrace properly even in release builds. Basically, if you don't know how the assertion failed, it would be almost impossible to diagnose the crash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, and I can appreciate that, but there can also be other constraints in play such as in embedded systems. You might want to disable all the extras and just handle errors that occur by restarting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add that in commercial software it's also desirable to hide all information about implementation details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@repax: You could easily redefine your own assert!
to hide every detail.
#[cfg(not(debug_assertions))] // <------
macro_rules! assert {
($e:expr) => {
if !$e {
panic!("assertion failure");
}
};
($e:expr, $($rest:tt)*) => {
assert!($e)
}
}
fn main() {
let a = 2;
let b = 2;
let c = 5;
assert!(a + b == c, "note: failed assertion {} + {} == {}", a, b, c);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kennytm: Why not just make this a compile-time option? Sometimes you want this debugging convenience, often times not. In designing features like this I think it's important to take into account the breadth of contexts in which rust code may operate in the wild.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@repax Define a cargo feature and use #[cfg(feature = "redacted_assert")]
then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My macro probably wouldn't apply to 3rd party crates, so this doesn't quite solve the bloat issue.
Thanks for the update @ishitatsuyuki! |
We discussed this RFC in the libs team triage today -- and this synopsis in particular. So far we are on board with this RFC but we insist that it must not make Please update the RFC to mention that the performance degradation under drawbacks refers to debug builds only. Also please update the Summary section to provide a better idea of what this RFC proposes. Generic refers to something different. I would mention that we want to implement |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
The final comment period is now complete. |
And with that, this RFC is now merged! Thanks @ishitatsuyuki! |
`assert_eq` will eventually be deprecated as per RFC 2011 (rust-lang/rfcs#2011). Signed-off-by: JP-Ellis <josh@jpellis.me>
`assert_eq` will eventually be deprecated as per RFC 2011 (rust-lang/rfcs#2011). Signed-off-by: JP-Ellis <josh@jpellis.me>
Pre-RFC
Rendered