You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/routes/blog/2025-09-01-zero-copying-strings-serde/+page.svx
+41-5Lines changed: 41 additions & 5 deletions
Original file line number
Diff line number
Diff line change
@@ -48,7 +48,7 @@ called `Result::unwrap()` on an `Err` value: Error("invalid type: string \"Go to
48
48
49
49
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!
50
50
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:
52
52
53
53
```rust
54
54
use serde::Deserialize;
@@ -68,13 +68,49 @@ fn main() {
68
68
}
69
69
```
70
70
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.
- [JSON string with backslashes does not deserialize into borrowed `&str`](https://github.com/serde-rs/serde/issues/1746)
77
77
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
79
79
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