-
Notifications
You must be signed in to change notification settings - Fork 371
Make Addr::unchecked a const fn #869
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,9 +1,10 @@ | ||||||||||||
#![allow(deprecated)] | ||||||||||||
|
||||||||||||
use schemars::JsonSchema; | ||||||||||||
use serde::{Deserialize, Serialize}; | ||||||||||||
use serde::{de, ser, Deserialize, Deserializer, Serialize}; | ||||||||||||
use std::fmt; | ||||||||||||
use std::ops::Deref; | ||||||||||||
use std::str; | ||||||||||||
|
||||||||||||
use crate::binary::Binary; | ||||||||||||
|
||||||||||||
|
@@ -23,8 +24,18 @@ use crate::binary::Binary; | |||||||||||
/// This type is immutable. If you really need to mutate it (Really? Are you sure?), create | ||||||||||||
/// a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` | ||||||||||||
/// instance. | ||||||||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash, JsonSchema)] | ||||||||||||
pub struct Addr(String); | ||||||||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, JsonSchema)] | ||||||||||||
pub struct Addr(#[schemars(with = "String")] AddrInner); | ||||||||||||
|
||||||||||||
/// The maximum allowed size for bech32 (https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32) | ||||||||||||
const ADDR_MAX_LENGTH: usize = 90; | ||||||||||||
|
||||||||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I want to reflect on the performance changes of this. We should add some guidance on usage.
With this in mind, I think we can recommend returning There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, if we pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would like to play around with this a bit next week. The size of We can always push them to heap by using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the problem you are trying to solve? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Make using owned Addr easier, especially literals. Then in many places you suddenly get an Addr->&str conversion instead of the opposite direction. I'm not claiming this solves all issues and it is well possible that it does not replace what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I didn't have any issues there. And am afraid I will have to think way too much about unneeded copies of this struct if it happens. See #872 for another solution that provide simple literals for tests |
||||||||||||
struct AddrInner { | ||||||||||||
/// UTF-8 encoded human readable string | ||||||||||||
data: [u8; ADDR_MAX_LENGTH], | ||||||||||||
length: usize, | ||||||||||||
} | ||||||||||||
|
||||||||||||
impl Addr { | ||||||||||||
/// Creates a new `Addr` instance from the given input without checking the validity | ||||||||||||
|
@@ -42,49 +53,111 @@ impl Addr { | |||||||||||
/// let address = Addr::unchecked("foobar"); | ||||||||||||
/// assert_eq!(address, "foobar"); | ||||||||||||
/// ``` | ||||||||||||
pub fn unchecked<T: Into<String>>(input: T) -> Addr { | ||||||||||||
Addr(input.into()) | ||||||||||||
pub const fn unchecked(input: &str) -> Addr { | ||||||||||||
let input_bytes = input.as_bytes(); | ||||||||||||
let mut data = [0u8; ADDR_MAX_LENGTH]; | ||||||||||||
let mut i = 0; | ||||||||||||
while i < input_bytes.len() { | ||||||||||||
maurolacy marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
data[i] = input_bytes[i]; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can buffer overflow if we don't have a length assertion for input |
||||||||||||
i += 1; | ||||||||||||
} | ||||||||||||
Comment on lines
+60
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I think we can do this here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here we are very limited in what is available because it must be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thinking about optimizing it, I looked at the implementation of eg. https://doc.rust-lang.org/core/ptr/fn.copy_nonoverlapping.html but that just point to rust intrinsics, so I guess this is the best we can do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wow – seems like we can use |
||||||||||||
|
||||||||||||
Addr(AddrInner { | ||||||||||||
data, | ||||||||||||
length: input_bytes.len(), | ||||||||||||
}) | ||||||||||||
} | ||||||||||||
|
||||||||||||
pub fn as_bytes(&self) -> &[u8] { | ||||||||||||
&self.0.data[..self.0.length] | ||||||||||||
} | ||||||||||||
|
||||||||||||
pub fn as_str(&self) -> &str { | ||||||||||||
unsafe { str::from_utf8_unchecked(self.as_bytes()) } | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
impl fmt::Display for Addr { | ||||||||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||||||||
write!(f, "{}", &self.0) | ||||||||||||
write!(f, "{}", self.as_str()) | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
impl Serialize for Addr { | ||||||||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||||||||||||
where | ||||||||||||
S: ser::Serializer, | ||||||||||||
{ | ||||||||||||
serializer.serialize_str(&self.as_str()) | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
impl<'de> Deserialize<'de> for Addr { | ||||||||||||
fn deserialize<D>(deserializer: D) -> Result<Addr, D::Error> | ||||||||||||
where | ||||||||||||
D: Deserializer<'de>, | ||||||||||||
{ | ||||||||||||
deserializer.deserialize_str(AddrVisitor) | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
struct AddrVisitor; | ||||||||||||
|
||||||||||||
impl<'de> de::Visitor<'de> for AddrVisitor { | ||||||||||||
type Value = Addr; | ||||||||||||
|
||||||||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { | ||||||||||||
formatter.write_str("Human readable address string") | ||||||||||||
} | ||||||||||||
|
||||||||||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> | ||||||||||||
where | ||||||||||||
E: de::Error, | ||||||||||||
{ | ||||||||||||
if v.len() > ADDR_MAX_LENGTH { | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice to assert this here. |
||||||||||||
Err(E::custom(format!( | ||||||||||||
"Input too long to be stored in an Addr. Max: {}, actual: {}", | ||||||||||||
ADDR_MAX_LENGTH, | ||||||||||||
v.len() | ||||||||||||
))) | ||||||||||||
} else { | ||||||||||||
Ok(Addr::unchecked(v)) | ||||||||||||
} | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
impl AsRef<str> for Addr { | ||||||||||||
#[inline] | ||||||||||||
fn as_ref(&self) -> &str { | ||||||||||||
&self.0 | ||||||||||||
self.as_str() | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
/// Implement `Addr == &str` | ||||||||||||
impl PartialEq<&str> for Addr { | ||||||||||||
fn eq(&self, rhs: &&str) -> bool { | ||||||||||||
self.0 == *rhs | ||||||||||||
self.as_str() == *rhs | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
/// Implement `&str == Addr` | ||||||||||||
impl PartialEq<Addr> for &str { | ||||||||||||
fn eq(&self, rhs: &Addr) -> bool { | ||||||||||||
*self == rhs.0 | ||||||||||||
*self == rhs.as_str() | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
/// Implement `Addr == String` | ||||||||||||
impl PartialEq<String> for Addr { | ||||||||||||
fn eq(&self, rhs: &String) -> bool { | ||||||||||||
&self.0 == rhs | ||||||||||||
self.as_str() == rhs | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
/// Implement `String == Addr` | ||||||||||||
impl PartialEq<Addr> for String { | ||||||||||||
fn eq(&self, rhs: &Addr) -> bool { | ||||||||||||
self == &rhs.0 | ||||||||||||
self == rhs.as_str() | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
|
@@ -93,25 +166,25 @@ impl PartialEq<Addr> for String { | |||||||||||
|
||||||||||||
impl From<Addr> for String { | ||||||||||||
fn from(addr: Addr) -> Self { | ||||||||||||
addr.0 | ||||||||||||
addr.as_str().to_owned() | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we also call this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
impl From<&Addr> for String { | ||||||||||||
fn from(addr: &Addr) -> Self { | ||||||||||||
addr.0.clone() | ||||||||||||
addr.as_str().to_owned() | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
impl From<Addr> for HumanAddr { | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we remove these - let's not support HumanAddr with the new code (and if someone wants that can always do |
||||||||||||
fn from(addr: Addr) -> Self { | ||||||||||||
HumanAddr(addr.0) | ||||||||||||
HumanAddr(addr.into()) | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
impl From<&Addr> for HumanAddr { | ||||||||||||
fn from(addr: &Addr) -> Self { | ||||||||||||
HumanAddr(addr.0.clone()) | ||||||||||||
HumanAddr(addr.into()) | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
|
@@ -268,7 +341,7 @@ mod tests { | |||||||||||
#[test] | ||||||||||||
fn addr_unchecked_works() { | ||||||||||||
let a = Addr::unchecked("123"); | ||||||||||||
let aa = Addr::unchecked(String::from("123")); | ||||||||||||
let aa = Addr::unchecked("123"); | ||||||||||||
let b = Addr::unchecked("be"); | ||||||||||||
maurolacy marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
assert_eq!(a, aa); | ||||||||||||
assert_ne!(a, b); | ||||||||||||
|
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.
The two level nesting here is for schemars output?
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.
Eaxtly. If we find a better way to ensure it gets a "string" schema, we can simplify at any time because all the internals are private.