Skip to content

Commit cd36adf

Browse files
authored
Mention Cow<str> and how #[serde(borrow)] is required in zero-copying strings post (#16)
Fixes #15, thank you @korrat!
1 parent 698b1f6 commit cd36adf

File tree

1 file changed

+41
-5
lines changed
  • src/routes/blog/2025-09-01-zero-copying-strings-serde

1 file changed

+41
-5
lines changed

src/routes/blog/2025-09-01-zero-copying-strings-serde/+page.svx

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ called `Result::unwrap()` on an `Err` value: Error("invalid type: string \"Go to
4848

4949
When deserializing the text, `serde_json` needs to convert `Go to C:\\Users\\bd\\Desktop` to `Go to C:\Users\bd\Desktop`. The only way it can do that is by *allocating a new string*. `serde_json` can't do that here, however, because we told it not to by using zero-copy deserialization!
5050

51-
In order to fix this, you need to replace the borrowed `&str` with an owned `String`[^1]. It can be slower than zero-copy deserialization, but it supports *all* possible data inputs:
51+
In order to fix this, you need to replace the borrowed `&str` with an owned `String`. It can be slower than zero-copy deserialization, but it supports *all* possible data inputs:
5252

5353
```rust
5454
use serde::Deserialize;
@@ -68,13 +68,49 @@ fn main() {
6868
}
6969
```
7070

71-
This kind of issue will arise when deserializing other escape codes in JSON, such as `\n` and `\t`. It can also occur when using other types that can be zero-copied, such as [`&Path`](https://doc.rust-lang.org/stable/std/path/struct.Path.html)[^2]. Next time you consider using zero-copy deserialization, be sure you're ok with limiting what data you can support.
71+
This kind of issue will arise when deserializing other escape codes in JSON, such as `\n` and `\t`. It can also occur when using other types that can be zero-copied, such as [`&Path`](https://doc.rust-lang.org/stable/std/path/struct.Path.html)[^1]. Next time you consider using zero-copy deserialization, be sure you're ok with limiting what data you can support.
7272

73-
**Further Reading:**
73+
## Further Reading:
7474

7575
- [Deserializer lifetimes](https://serde.rs/lifetimes.html)
7676
- [JSON string with backslashes does not deserialize into borrowed `&str`](https://github.com/serde-rs/serde/issues/1746)
7777

78-
[^1]: You can also use [`Cow<str>`](https://doc.rust-lang.org/stable/std/borrow/enum.Cow.html), but it will allocate a new `String` even if the text can be zero-copied, so it has the same effect as just using `String` directly.
78+
## Addendum
7979

80-
[^2]: Be especially careful about using this type. Since it cannot deserialize backslashes, you're essentially eliminating support for Windows paths.
80+
As [@korrat](https://github.com/korrat) has [helpfully pointed out](https://github.com/BD103/bd103.github.io/issues/15), [`Cow<str>`](https://doc.rust-lang.org/stable/std/borrow/enum.Cow.html) can be used as a compromise between `&str` and `String`. If you annotate a field with `#[serde(borrow)]`, it will first try to zero-copy deserialize the string, but will fall back to cloning the data if it needs to be modified.
81+
82+
As a result, `Cow<str>` should be preferred as it offers performance improvements without restricting what data can be deserialized:
83+
84+
```rust
85+
use serde::Deserialize;
86+
use serde_json;
87+
88+
use std::borrow::Cow;
89+
90+
#[derive(Deserialize)]
91+
struct Foo<'a> {
92+
// Try to borrow the string when possible, but clone it when necessary.
93+
#[serde(borrow)]
94+
text: Cow<'a, str>,
95+
}
96+
97+
fn main() {
98+
// No changes need to be made, the string can be borrowed.
99+
let json = r#"{ "text": "Hello, world!" }"#;
100+
101+
let foo: Foo = serde_json::from_str(json).unwrap();
102+
103+
assert!(matches!(foo.text, Cow::Borrowed(_)));
104+
105+
// Changes need to be made, the string must be owned.
106+
let json = r#"{ "text": "Hello,\nworld!" }"#;
107+
108+
let foo: Foo = serde_json::from_str(json).unwrap();
109+
110+
assert!(matches!(foo.text, Cow::Owned(_)));
111+
}
112+
```
113+
114+
In the initial release of this post, I incorrectly wrote that `Cow<str>` does not support zero-copy deserialization. This isn't the case, you just need to annotate the `Cow<str>` field with `#[serde(borrow)]`. Don't forget that attribute, or `Cow<str>` will just be equivalent to `String`! See [the `serde` docs](https://serde.rs/lifetimes.html#borrowing-data-in-a-derived-impl) for more information and examples.
115+
116+
[^1]: Be especially careful about using this type. Since it cannot deserialize backslashes, you're essentially eliminating support for Windows paths.

0 commit comments

Comments
 (0)