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
Remove calls to debug_assert #953
Conversation
@@ -240,7 +240,6 @@ macro_rules! decoder_fn { | |||
($name:ident, $val_type:ty, $readfn:ident, $byte_len: expr) => { | |||
#[inline] | |||
fn $name(&mut self) -> Result<$val_type, Error> { | |||
debug_assert_eq!(::core::mem::size_of::<$val_type>(), $byte_len); // size_of isn't a constfn in 1.22 |
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.
Why remove this?
Also, I'm pretty sure after the MSRV bump we can make this a compile-time check:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=44cce9ee0d3113cdaaaf7c6f421d5699
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.
Cool. I'm no macro expert but is this as good or am I missing something?
macro_rules! const_assert {
($x:expr) => {
const _UNUSED: [(); 0 - !$x as usize] = [];
};
}
This works with Rust 1.29 as well.
(_UNUSED
instead of _
because of Rust 1.29)
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.
OoO I didn't think that this can be done in 1.29!
That will work but will limit the macro to work only once in every scope, otherwise, you'll get multiple definitions of _UNUSED
. but it doesn't seem like we ever const-assert more than one thing in the same scope
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.
Just to double check, is the simplification of the original
[(); 0 - !{ const ASSERT: bool = $x; ASSERT } as usize] = []
to
[(); 0 - !$x as usize] = []
ok? I'm going to force push this change but wanted to explicitly double check with you. Thanks
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.
Yes :)
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.
ACK e2fff70
I also don't see the value in removing the size checks, but OTOH I don't see a lot of value in keeping them.
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.
At the risk of being a dick for jumping in late, I'm not sure why we'd want this - I don't have a deep understanding of PSBT, but do we really want to panic (and likely crash the user application) if we end up accidentally serializing a PSBT slightly wrong? I view these kinds of debug assertions as test cases that are slipped into the main code body because its easier to put tests there sometimes. At least on the LDK end, our stance is "panic if we're gonna lose money, return error or continue awkwardly otherwise".
hmm, maybe an implicit benefit of using |
e2fff70
to
dc97d37
Compare
Changes in force push: Use @elichai's |
Yea, totally fair. Sometimes it is just easier to write a test by slipping a debug assertion into the code right where you need to check something, though, though maybe such changes are better in
Yea, fair, no opinion on these specific assertions, I'm not super in depth on the PSBT part of the codebase so don't really have an opinion. |
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.
ACK dc97d37
By the way I think const_assert
could be used repeatedly in the same scope if you added an extra scope inside the macro.
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.
utACK dc97d37
This PR is not dealing with any Psbt code. |
dc97d37
to
7acaa4b
Compare
Nice one! Changes in force push are to the first patch, added additional scope as suggested. No other changes. |
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.
Firstly, changing asserts to const is great!
Personal opinion follows: If an assert guards the inputs to the function (prevents invariants breaking) which can not be or would be to cumbersome to be implemented using newtypes it has to be non-debug assert!
. All else can be debug_
just fine. However if people like non-debug asserts since they are cheap, I'm not going to block it.
I prefer to remove the redundant one.
@@ -140,7 +140,7 @@ impl From<psbt::Error> for Error { | |||
pub fn serialize<T: Encodable + ?Sized>(data: &T) -> Vec<u8> { | |||
let mut encoder = Vec::new(); | |||
let len = data.consensus_encode(&mut encoder).expect("in-memory writers don't error"); | |||
debug_assert_eq!(len, encoder.len()); | |||
assert_eq!(len, encoder.len()); |
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.
Opinion: debug_assert_eq
was fine here.
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.
FTR this is one of the ones that @TheBlueMatt alluded to in saying this is really a test of the consensus_encode
function and as such belongs in a test not here in a debug build.
re debug_assert
vs assert
, and I'm just learning this this week, I have been attempting to follow the Rust docs which state that debug_assert
should only be used if the code was profiled and assert
was found to be a bottle neck. Do you disagree with this @Kixunil?
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.
debug_assert should only be used if the code was profiled and assert was found to be a bottle neck.
I'm not sure I like that line? I mean I'm not sure why we should be crashing the users' application because we're doing something wrong. Its not really a big deal, like hopefully this stuff is all right and we wont hit it anyway, but I've never heard of "only use asserts if they're slow" - coming from C, -DNDEBUG is a thing that (usually) disables all assert()s.
@@ -78,7 +78,7 @@ impl Encodable for CommandString { | |||
fn consensus_encode<S: io::Write>(&self, s: S) -> Result<usize, io::Error> { | |||
let mut rawbytes = [0u8; 12]; | |||
let strbytes = self.0.as_bytes(); | |||
debug_assert!(strbytes.len() <= 12); | |||
assert!(strbytes.len() <= 12); | |||
rawbytes[..strbytes.len()].copy_from_slice(strbytes); |
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.
Slicing already performs this check. assert
would've been better if the resulting error message was clearer than the default which I don't think is the case here. AFAIR out-of-bounds slicing prints the lengths while assert
-ing like this doesn't. Thus this assert only has disadvantages and it's best to remove it completely.
(Side note: an assert like this could have been good before a loop.)
OT: maybe we could serialize the slice directly and fill in extra zeroes to avoid a bunch of copies. Something for opt nerds. :)
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.
Will revisit this one after other discussion no the PR resolves. Thanks.
@@ -429,7 +429,7 @@ impl TaprootBuilder { | |||
} | |||
// Every iteration of the loop reduces the node_weights.len() by exactly 1 | |||
// Therefore, the loop will eventually terminate with exactly 1 element | |||
debug_assert_eq!(node_weights.len(), 1); | |||
assert_eq!(node_weights.len(), 1); |
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.
Opinion: debug was fine here.
That's cool, the downside of that though is that it can't be used out of scope :) (outside of a function) |
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.
ACk 7acaa4b. I can see arguments for both debug_assert
and assert
, but I think we all agree that for this PR it is an improvement?.
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.
ACK 7acaa4b
But sounds like there are still some open issues?
Changing to draft, this was only meant to be a quick thing and as it has uncovered a can of worms I'll put it on the back burner till after the MSRV chaos is over. |
Add a macro `const_assert` that uses some const declaration trickery to trigger a compile time error if a boolean expression is false. Replace runtime checks using `debug_assert_eq!` with the newly defined `const_asert!` macro.
According to the Rust docs for `debug_assert` it should only be used if there a proved performance costs to using `assert`. Checking the length of a `BinaryHeap` is equivalent to checking the length of a `Vec` which is fast, therefore its unlikely that using `assert_eq` will have any noticeable performance costs.
According to the Rust docs for `debug_assert` it should only be used if there a proved performance costs to using `assert`. Checking the length of the slice returned by `as_bytes` is fast, therefore its unlikely that using `assert_eq` will have any noticeable performance costs.
According to the Rust docs for `debug_assert` it should only be used if there a proved performance costs to using `assert`. Checking the length of a vector is fast, therefore its unlikely that using `assert_eq` will have any noticeable performance costs.
7acaa4b
to
15d3800
Compare
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 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'm not really sure I understand why do this? These assertions mostly don't seem like the kind of thing that's going to result in a user losing money, and in such a case it doesn't seem worth crashing the users' app?
@TheBlueMatt if these ever trigger then we (the developers) have gotten very confused about what state the library is in. I don't expect that they will ever trigger. You are correct that these are essentially unit tests, and maybe shouldn't be inline in the code (though they are easier to find and understand this way), which is why they were originally |
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.
ACK 15d3800
but still looks like there is disagreement about whether we want this PR
Yep, we're on the same page.
Yep, we're on the same page.
Yep, I'm fine with it, I just wanted to point out that I don't think "never debug_assert unless its a performance loss" is the right line to draw. While rust-bitcoin is lower level so isn't as clear in general, on the LDK/rust-lightning side of things we try to stick with "asserts are for where we're in a state where we may have lost/may lose money, debug_asserts are for everything else". Indeed, because sometimes its just way easier to slip in unit tests in-line, we have some nontrivial debug assertions in-line to check consistency of somewhat complex state. Indeed, some of them used to be regular asserts and we had users hit them in production. Again, rust-bitcoin is a little harder to make that distinction with - if a serialization call returns the wrong length can a user end up signing something with too little fee and lose money cause they had some CLTV timelock they had to meet? You can arguably make a similar argument for most parts of rust-bitcoin, so I'm not really against rust-bitcoin going down the "always assert" path, just making sure we're drawling a line that makes sense. |
Closing this since it is contentious. |
Perhaps reconsider doing const asserts only, I think they'd be non-contentious. (At least I think it'd be great and never heard anyone arguing against them.) |
Done: #1000 PR 1000, BOOM! |
7a3bb7d Replace runtime size check with compile time check (Tobin C. Harding) Pull request description: Add a macro `const_assert` that uses some const declaration trickery to trigger a compile time error if a boolean expression is false. Replace runtime checks using `debug_assert_eq!` with the newly defined `const_asert!` macro. ## Note This PR is the first patch from a [recently closed PR](#953). Props to @elichai for the macro idea in the review of that PR. ACKs for top commit: Kixunil: ACK 7a3bb7d apoelstra: ACK 7a3bb7d Tree-SHA512: cfd4dcf6c66e06796cab6dc49445f0f8c5d4e686893a17735420dccedd75ad7c632d240a5ab92ee47ce459b799daeaf3fdf9c6b77c1b81b09e87197a9f86c5ba
… compile time check 7a3bb7d Replace runtime size check with compile time check (Tobin C. Harding) Pull request description: Add a macro `const_assert` that uses some const declaration trickery to trigger a compile time error if a boolean expression is false. Replace runtime checks using `debug_assert_eq!` with the newly defined `const_asert!` macro. ## Note This PR is the first patch from a [recently closed PR](rust-bitcoin/rust-bitcoin#953). Props to @elichai for the macro idea in the review of that PR. ACKs for top commit: Kixunil: ACK 7a3bb7d apoelstra: ACK 7a3bb7d Tree-SHA512: cfd4dcf6c66e06796cab6dc49445f0f8c5d4e686893a17735420dccedd75ad7c632d240a5ab92ee47ce459b799daeaf3fdf9c6b77c1b81b09e87197a9f86c5ba
Done after reading discussion on #853.
Audit the code for calls to the various
debug_assert
macros. Check if the call is likely to be add any performance costs and if not use the equivalentassert
macro.Done as separate patches so that each change can be fully described by the commit message.
With this PR applied there is one instance of
debug_assert!
left, the call checks against the result oftweak_add_check
. I do not know if this call is fast or slow so I left it as it is.