-
Notifications
You must be signed in to change notification settings - Fork 51
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
hrp: add constructor from an arbitrary T: Display #196
hrp: add constructor from an arbitrary T: Display #196
Conversation
|
||
let mut byte_formatter = ByteFormatter { arr: [0; MAX_HRP_LEN], index: 0, error: None }; | ||
|
||
write!(byte_formatter, "{}", data).expect("custom Formatter cannot fail"); |
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.
Doesn't this macro call have to allocate a String
because data
is not know until runtime?
My understanding is that write!
calls format_args!
which creates an Arguments type which has an as_str
method but only if there are no runtime args.
In other words isn't this the same performance as:
pub fn parse_display<T: core::fmt::Display>(data: T) -> Result<Self, Error> {
let s = data.to_string();
Self::parse(&s)
}
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.
This code works without any allocator. It's not allocating a string. It would also be insane for the fmt::Write
API to work this way, where every single call to write_str
involved a gratuitous allocation.
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.
Or indeed, for every call to write!
anywhere to involve gratuitous allocations.
If you think this could be true we'd need to change our fmt::Display
implementations throughout every one of our crates to avoid this 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.
It took me a little while to trace through the code but the core of write!
is the write
function which iterates over the "pieces" of an Arguments
and does not allocate.
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, TIL. Thanks for the link (I was unconvinced before it, you saved me trawling through the source :)
src/primitives/hrp.rs
Outdated
for b in s.bytes() { | ||
// Continue after finding an error so that we report the first invalid | ||
// character, not the last. | ||
if !b.is_ascii() { |
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 are a few problems here, I uncovered them by writing a test to check the code comment (which is incorrect, we return the last error), I think we want break
instead of continue
.
Also we need to iterate over chars
and check they are ascii instead of using bytes
- we do this in parse
above.
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.
Instead of commenting each line I just wrote some tests that I think should pass
#[test]
fn parse_non_ascii() {
assert_eq!(Hrp::parse("❤").unwrap_err(), Error::NonAsciiChar('❤'));
}
#[test]
fn parse_display_non_ascii() {
assert_eq!(Hrp::parse("❤").unwrap_err(), Error::NonAsciiChar('❤'));
}
#[test]
fn parse_display_returns_first_error() {
assert_eq!(Hrp::parse_display("❤ ").unwrap_err(), Error::NonAsciiChar('❤'));
}
// This test shows that the error does not contain heart.
#[test]
fn parse_display_iterates_chars() {
// FIXME: Once test above passes the error char will need changing.
assert_eq!(Hrp::parse_display(" ❤").unwrap_err(), Error::NonAsciiChar('❤'));
}
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.
Thanks! I will take these tests and make them pass.
cda45eb
to
2fdc674
Compare
Should be good now. |
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 2fdc674
// Unconditionally update self.index so that in the case of a too-long | ||
// string, our error return will reflect the full length. |
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.
nit: This comment is stale now I believe.
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 think we could just check the length at the start and early return.
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 don't see why this is stale. The line is called every time the write_str
function is called, for the reason that the comment describes.
We could do a length check at the start and early return but I don't see what that would accomplish other than increasing the cyclotomic complexity of the 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.
ooo, I see. Don't mind me.
I think we may want to backport this to 0.11, since when we release 0.12, rust-bitcoin 0.32 won't be able to use it (thanks to rust-bitcoin/rust-bitcoin#3043). Same with #197, so let's wait until that's done as well. |
src/primitives/hrp.rs
Outdated
|
||
#[test] | ||
fn parse_display_non_ascii() { | ||
assert_eq!(Hrp::parse("❤").unwrap_err(), Error::NonAsciiChar('❤')); |
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.
Should this call parse_display
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.
Oops, yes! Fixed.
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.
A bug in the unit tests, what are the odds ;)
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.
Oh actually, if this is based on the unit test code I posted above I intentionally included one that parsed using Hrp::parse
.
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, you intentionally included one, but you unintentionally included another one :).
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.
Sweet, cheers. Its hard to get good help :)
2fdc674
to
11d6cb4
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.
ACK 11d6cb4
Fixes #195