From 694a6d2852d4fb26e4485b78dfb2a7560572ea93 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Sat, 22 Aug 2020 11:56:14 +0200 Subject: [PATCH] Optimize Address implementation This reduces the mem::size_of::
() from 72 to 32 and removes two heap allocations of String when constructing a new instance of Address. --- src/address.rs | 74 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/src/address.rs b/src/address.rs index c743440..665c82c 100644 --- a/src/address.rs +++ b/src/address.rs @@ -19,32 +19,30 @@ use std::{ /// **NOTE**: Enable feature "serde" to be able serialize/deserialize it using [serde](https://serde.rs/). #[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)] pub struct Address { - /// User part - pub user: String, - /// Domain part - pub domain: String, /// Complete address - complete: String, + serialized: String, + /// Index into `serialized` before the '@' + at_start: usize, } impl TryFrom<(U, D)> for Address where - U: Into, - D: Into, + U: AsRef, + D: AsRef, { type Error = AddressError; - fn try_from(from: (U, D)) -> Result { - let (user, domain) = from; - let user = user.into(); - Address::check_user(&user)?; - let domain = domain.into(); - Address::check_domain(&domain)?; - let complete = format!("{}@{}", &user, &domain); + fn try_from((user, domain): (U, D)) -> Result { + let user = user.as_ref(); + Address::check_user(user)?; + + let domain = domain.as_ref(); + Address::check_domain(domain)?; + + let serialized = format!("{}@{}", user, domain); Ok(Address { - user, - domain, - complete, + serialized, + at_start: user.len(), }) } } @@ -65,10 +63,20 @@ static LITERAL_RE: Lazy = Lazy::new(|| Regex::new(r"(?i)\[([A-f0-9:\.]+)\ impl Address { /// Create email address from parts - pub fn new, D: Into>(user: U, domain: D) -> Result { + pub fn new, D: AsRef>(user: U, domain: D) -> Result { (user, domain).try_into() } + /// Get the user part of this `Address` + pub fn user(&self) -> &str { + &self.serialized[..self.at_start] + } + + /// Get the domain part of this `Address` + pub fn domain(&self) -> &str { + &self.serialized[self.at_start + 1..] + } + fn check_user(user: &str) -> Result<(), AddressError> { if USER_RE.is_match(user) { Ok(()) @@ -104,7 +112,7 @@ impl Address { impl Display for Address { fn fmt(&self, f: &mut Formatter) -> FmtResult { - f.write_str(&self.complete) + f.write_str(&self.serialized) } } @@ -119,22 +127,21 @@ impl FromStr for Address { Address::check_user(user)?; Address::check_domain(domain)?; Ok(Address { - user: user.into(), - domain: domain.into(), - complete: val.to_string(), + serialized: val.into(), + at_start: user.len(), }) } } impl AsRef for Address { fn as_ref(&self) -> &str { - &self.complete.as_ref() + &self.serialized } } impl AsRef for Address { fn as_ref(&self) -> &OsStr { - self.complete.as_ref() + self.serialized.as_ref() } } @@ -176,7 +183,7 @@ pub mod serde { where S: Serializer, { - serializer.serialize_str(&self.to_string()) + serializer.serialize_str(self.as_ref()) } } @@ -274,3 +281,20 @@ pub mod serde { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_address() { + let addr_str = "something@example.com"; + let addr = Address::from_str(addr_str).unwrap(); + let addr2 = Address::new("something", "example.com").unwrap(); + assert_eq!(addr, addr2); + assert_eq!(addr.user(), "something"); + assert_eq!(addr.domain(), "example.com"); + assert_eq!(addr2.user(), "something"); + assert_eq!(addr2.domain(), "example.com"); + } +}