Skip to content

Commit e7f086c

Browse files
ipnetwork: Deserialize str instead of allocating
Currently, `ipnetwork` always allocates during deserialization. This is unfortunate because ownership isn't needed to construct any of `ipnetwork`'s types. I implemented a small Visitor that opportunistically deserializes from a str borrowed from the deserializer. For some formats, this should help zero alloc deserialization. While some formats will still allocate and pass the deserializer a &str, this Visitor still avoids the second alloc from String in the original.
1 parent 479b29f commit e7f086c

4 files changed

Lines changed: 40 additions & 6 deletions

File tree

ipnetwork/src/ipv4.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ impl<'de> serde::Deserialize<'de> for Ipv4Network {
2020
where
2121
D: serde::Deserializer<'de>,
2222
{
23-
let s = <String>::deserialize(deserializer)?;
24-
Ipv4Network::from_str(&s).map_err(serde::de::Error::custom)
23+
deserializer.deserialize_str(crate::serde_helpers::FromStrVisitor(core::marker::PhantomData))
2524
}
2625
}
2726

ipnetwork/src/ipv6.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ impl<'de> serde::Deserialize<'de> for Ipv6Network {
2121
where
2222
D: serde::Deserializer<'de>,
2323
{
24-
let s = <String>::deserialize(deserializer)?;
25-
Ipv6Network::from_str(&s).map_err(serde::de::Error::custom)
24+
deserializer.deserialize_str(crate::serde_helpers::FromStrVisitor(core::marker::PhantomData))
2625
}
2726
}
2827

ipnetwork/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ mod error;
1919
mod ipv4;
2020
mod ipv6;
2121
mod parse;
22+
mod serde_helpers;
2223
mod size;
2324

2425
pub use crate::{
@@ -42,8 +43,7 @@ impl<'de> serde::Deserialize<'de> for IpNetwork {
4243
where
4344
D: serde::Deserializer<'de>,
4445
{
45-
let s = <String>::deserialize(deserializer)?;
46-
IpNetwork::from_str(&s).map_err(serde::de::Error::custom)
46+
deserializer.deserialize_str(crate::serde_helpers::FromStrVisitor(core::marker::PhantomData))
4747
}
4848
}
4949

ipnetwork/src/serde_helpers.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#![cfg(feature = "serde")]
2+
3+
//! Useful [`serde`] helpers.
4+
5+
use core::{fmt, marker::PhantomData, str::FromStr};
6+
7+
use serde::de::{Error as DeError, Visitor};
8+
9+
/// Deserialize a struct implementing [`FromStr`] without allocating a temporary String.
10+
pub(crate) struct FromStrVisitor<F>(pub PhantomData<F>);
11+
12+
impl<'de, F> Visitor<'de> for FromStrVisitor<F>
13+
where
14+
F: FromStr,
15+
F::Err: fmt::Display,
16+
{
17+
type Value = F;
18+
19+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
20+
write!(formatter, "a network as a string")
21+
}
22+
23+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
24+
where
25+
E: DeError,
26+
{
27+
F::from_str(v).map_err(DeError::custom)
28+
}
29+
30+
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
31+
where
32+
E: DeError,
33+
{
34+
F::from_str(v).map_err(DeError::custom)
35+
}
36+
}

0 commit comments

Comments
 (0)