the trait bound Rc<std::string::String>: serde::Serialize is not satisfied
主线位置:不在三条主线上,是进入序列化主线之前的编译期检查。
Rc/Arc的Serialize和Deserialize存在,但被#[cfg(feature = "rc")]挡住了。机制见 标准库类型的实现 的「Box、Rc、Arc:共享不会被保留」。
flowchart TB
use["字段里有 Rc 或 Arc"] --> feat{"serde 开了 rc 吗"}
feat -->|没开| e0277["E0277 not satisfied"]
feat -->|开了| ser["按内容各写一份"]
ser --> de["读回来每个计数为 1"]
de --> keep{"要保留共享吗"}
keep -->|不要| done["可以用"]
keep -->|要| ids["线上用编号自己重建"]
deref_impl! 怎么让 Rc<T> 解引用后整份序列化、box_forwarded_impl! 怎么经 Box 造出新的 Rc、rc feature 是 1.0.0 为什么加的,在 标准库类型的实现 里讲过、跑过。这一页讲报错长什么样、开 feature 之后还剩什么问题、以及「我没开却能编译」是怎么回事。
本页实验在 M2 Max、macOS、rustc 1.99.0 上运行。报错的实验在一个只依赖 serde = { version = "=1.0.229", features = ["derive"] }、serde_json = "=1.0.154" 的 crate 里;其余实验的 serde 加了 rc。
现象
src/bin/rc_derive.rs:
use std::rc::Rc;
use serde::Serialize;
#[derive(Serialize)]
struct Post {
title: Rc<String>,
}
fn main() {
let p = Post { title: Rc::new("hello".to_string()) };
println!("{}", serde_json::to_string(&p).unwrap());
}
error[E0277]: the trait bound `Rc<std::string::String>: serde::Serialize` is not satisfied
--> src/bin/rc_derive.rs:5:10
|
5 | #[derive(Serialize)]
| ^^^^^^^^^ the trait `Serialize` is not implemented for `Rc<std::string::String>`
6 | struct Post {
7 | title: Rc<String>,
| ----- required by a bound introduced by this call
|
= note: for local types consider adding `#[derive(serde::Serialize)]` to your `Rc<std::string::String>` type
= note: for types from other crates check whether the crate offers a `serde` feature flag
= help: the following other types implement trait `Serialize`:
&'a T
&'a mut T
()
(T,)
(T0, T1)
(T0, T1, T2)
(T0, T1, T2, T3)
(T0, T1, T2, T3, T4)
and 130 others
note: required by a bound in `_serde::ser::SerializeStruct::serialize_field`
--> ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde_core-1.0.229/src/ser/mod.rs:1917:21
|
1915 | fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error>
| --------------- required by a bound in this associated function
1916 | where
1917 | T: ?Sized + Serialize;
| ^^^^^^^^^ required by this bound in `SerializeStruct::serialize_field`
= note: this error originates in the derive macro `Serialize` (in Nightly builds, run with -Z macro-backtrace for more info)
For more information about this error, try `rustc --explain E0277`.
error: could not compile `exp-serde-pit3-norc` (bin "rc_derive") due to 1 previous error
- 两条 note 都在误导:第一条让你给
Rc加 derive(做不到),第二条让你查「别的 crate」的 feature,而Rc来自标准库,要开的是 serde 自己的rc。 - 位置指向
#[derive(Serialize)],required by a bound指向 derive 生成的serialize_field调用,字段名title在第 7 行被标了出来。
不经过 derive,直接序列化一个 Arc<[String]>(src/bin/rc_direct.rs),开头三行:
error[E0277]: the trait bound `Arc<[std::string::String]>: serde::Serialize` is not satisfied
--> src/bin/rc_direct.rs:5:40
|
5 | println!("{}", serde_json::to_string(&tags).unwrap());
| --------------------- ^^^^^ the trait `serde_core::ser::Serialize` is not implemented for `Arc<[std::string::String]>`
标题行写 serde::Serialize,下面的标签却写 serde_core::ser::Serialize:标题被 serde 改写过,标签是 rustc 自己拼的(见下面的冷知识)。Deserialize 一侧同理,#[derive(Deserialize)] struct Post { title: Rc<str> }(src/bin/rc_de.rs)报三条 the trait bound `Rc<str>: serde::Deserialize<'de>` is not satisfied,分别指向生成代码里的 next_element、next_value 和 missing_field。
出处
Rc<T> 的 Serialize 由 deref_impl! 生成,那一次调用带着 cfg:
#[cfg(all(feature = "rc", any(feature = "std", feature = "alloc")))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "rc", any(feature = "std", feature = "alloc")))))]
<T> Serialize for Rc<T> where T: ?Sized + Serialize
Arc、Weak(RcWeak 的 Serialize)、反序列化一侧的 box_forwarded_impl! 都是同一个条件。feature 在 serde 的 Cargo.toml 里定义为 rc = ["serde_core/rc"],旁边的注释写着:「Serializing and deserializing these types does not preserve identity and may result in multiple copies of the same data. Be sure that this is what you want before enabling this feature.」
报错标题来自 Serialize 上的 diagnostic::on_unimplemented:
diagnostic::on_unimplemented(
// Prevents `serde_core::ser::Serialize` appearing in the error message
// in projects with no direct dependency on serde_core.
message = "the trait bound `{Self}: serde::Serialize` is not satisfied",
note = "for local types consider adding `#[derive(serde::Serialize)]` to your `{Self}` type",
note = "for types from other crates check whether the crate offers a `serde` feature flag",
)
{Self} 由 rustc 填成完整路径 Rc<std::string::String>,所以标题里是 std::string::String 而不是你写的 String。
两条 note 来自 PR #2767(「Use the #[diagnostic::on_unimplemented] attribute when possible」,首次发布于 1.0.204),说明是:「This change enables the #[diagnostic::on_unimplemented] attribute for the Serialize and Deserialize trait to point the user to the relevant derives and point out that they might want to check crates features for external types」。那时只有 note,没有 message。
message 是 PR #2963(「Override diagnostic::on_unimplemented message for all serde_core traits」,首次发布于 1.0.220,也就是 serde_core 拆出来的那个版本)加的。trait 搬进 serde_core 之后,rustc 默认会把它写成 serde_core::ser::Serialize,而大多数项目根本没有直接依赖 serde_core。PR 说明:「This prevents diagnostics being rendered like serde_core::ser::Serialize and serde_core::de::Deserialize in projects that have no direct dependency on serde_core.」它也承认了代价:「The attributes make error messages arguably sometimes worse than pre-serde_core by always rendering serde::Serialize and never Serialize when use serde::Serialize is in scope」。所以上面的标题里即使已经 use serde::Serialize,也写着 serde::Serialize;而 message 管不到的标签行,在 rc_direct 里仍然露出 serde_core::ser::Serialize。
修法
在 serde 上开 rc。 Cargo.toml 写 serde = { version = "=1.0.229", features = ["derive", "rc"] },上面的程序就能编译。但开之前要确认你接受它的语义:序列化时每个 Rc 按内容各写一份,反序列化时每个 Rc 都是新的。src/bin/rc_roundtrip.rs:
use std::cell::RefCell;
use std::rc::{Rc, Weak};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct Pair {
a: Rc<RefCell<String>>,
b: Rc<RefCell<String>>,
}
#[derive(Serialize, Deserialize, Debug)]
struct Child {
name: String,
parent: Weak<String>,
}
fn main() {
let shared = Rc::new(RefCell::new("hello".to_string()));
let pair = Pair { a: shared.clone(), b: shared };
let json = serde_json::to_string(&pair).unwrap();
println!("json: {json}");
let back: Pair = serde_json::from_str(&json).unwrap();
back.a.borrow_mut().push_str(", world");
println!(
"before: ptr_eq={} strong={} | after: ptr_eq={} strong={} b={:?}",
Rc::ptr_eq(&pair.a, &pair.b),
Rc::strong_count(&pair.a),
Rc::ptr_eq(&back.a, &back.b),
Rc::strong_count(&back.a),
back.b.borrow(),
);
let parent = Rc::new("root".to_string());
let child = Child { name: "leaf".into(), parent: Rc::downgrade(&parent) };
let json = serde_json::to_string(&child).unwrap();
let back: Child = serde_json::from_str(&json).unwrap();
println!("Weak: json={json} upgrade after round trip={:?}", back.parent.upgrade());
}
json: {"a":"hello","b":"hello"}
before: ptr_eq=true strong=2 | after: ptr_eq=false strong=1 b="hello"
Weak: json={"name":"leaf","parent":"root"} upgrade after round trip=None
- 读回来的
a和b是两个分配,计数各为 1。改了a,b还是"hello":靠Rc<RefCell<_>>共享可变状态的代码,往返之后行为就变了。 Weak写出时先upgrade(),把父节点的内容整份写进了子节点;读回时把内容读完扔掉,返回Weak::new(),永远是None(RcWeak的Deserialize)。带父指针的树往返之后,父指针全断。
要保留共享,就在线上写编号。 serde 的数据模型里没有「对象标识」,只能在转换时自己去重、自己重建。src/bin/rc_ids.rs,两篇文章共用一个作者:
use std::rc::Rc;
use serde::{Deserialize, Serialize};
// In memory: two posts share one author.
struct Post {
title: String,
author: Rc<String>,
}
// On the wire: the author is stored once and referred to by index.
#[derive(Serialize, Deserialize)]
struct Wire {
authors: Vec<String>,
posts: Vec<(String, usize)>,
}
fn to_wire(posts: &[Post]) -> Wire {
let mut authors: Vec<Rc<String>> = vec![];
let mut out = vec![];
for p in posts {
let i = match authors.iter().position(|a| Rc::ptr_eq(a, &p.author)) {
Some(i) => i,
None => {
authors.push(p.author.clone());
authors.len() - 1
}
};
out.push((p.title.clone(), i));
}
Wire { authors: authors.iter().map(|a| a.to_string()).collect(), posts: out }
}
fn from_wire(w: Wire) -> Vec<Post> {
let authors: Vec<Rc<String>> = w.authors.into_iter().map(Rc::new).collect();
w.posts.into_iter().map(|(title, i)| Post { title, author: authors[i].clone() }).collect()
}
fn main() {
let alice = Rc::new("alice".to_string());
let posts = vec![
Post { title: "a".into(), author: alice.clone() },
Post { title: "b".into(), author: alice },
];
let json = serde_json::to_string(&to_wire(&posts)).unwrap();
let back = from_wire(serde_json::from_str(&json).unwrap());
println!("json: {json}");
println!("after: ptr_eq={} strong={}", Rc::ptr_eq(&back[0].author, &back[1].author), Rc::strong_count(&back[0].author));
}
json: {"authors":["alice"],"posts":[["a",0],["b",0]]}
after: ptr_eq=true strong=2
作者只写了一次,读回来两篇文章指向同一个 Rc。Post 本身不实现 serde 的 trait,参与序列化的 Wire 里没有 Rc,这条路不需要 rc feature。
只是为了便宜地 clone、不在乎共享语义的, 开 rc 就是正确的修法;Arc<str>、Arc<[T]> 这类不可变数据往返后内容不变,丢的只是「同一个分配」这件事。
没开 rc 也能编译?
feature 在整个依赖图里是合并的:任何一个依赖给 serde 开了 rc,所有用同一个 serde 的 crate 都能看到这些 impl。用一个两成员的工作区复现:enabler 依赖 serde = { version = "=1.0.229", features = ["rc"] },什么代码也没有;app 依赖 serde(只开 derive)、serde_json 和 enabler,main.rs 就是上面的 rc_derive.rs。
cargo run --offline -p app 的输出:
{"title":"hello"}
app 自己从没要过 rc,照样能编译运行。哪天 enabler 不再开 rc(或者 app 被单独拿出去),就会突然冒出本页标题这条错误。查是谁开的,用 cargo tree --offline -e features -i serde -p app,再 grep -A1 'feature "rc"' 过滤:
├── serde feature "rc"
│ └── enabler v0.1.0 (exp-serde-pit3/unify/enabler) (*)
作为对照,只开了 derive 的那个 crate 里,cargo tree --offline -i serde -f '{p} {f}' --depth 0 输出 serde v1.0.229 default,derive,serde_derive,std,没有 rc。
怎么确认没中招
- 用了
Rc/Arc字段的 crate,自己的Cargo.toml里要显式写features = ["rc"],不要靠别的依赖碰巧打开。cargo tree -e features -i serde能看到每个 feature 是谁开的;cargo tree -i serde -f '{p} {f}' --depth 0一行列出最终打开的全部 feature。 - 开了
rc的类型,写一个往返测试,断言你在意的那件事:Rc::ptr_eq、Rc::strong_count、Weak::upgrade是否为Some。这些在往返后都会变。 - 序列化体积异常大时,查一下是不是同一个
Rc被写了很多份。
小结
Rc、Arc、Weak的Serialize/Deserialize都在#[cfg(feature = "rc")]后面;不开时报the trait bound `Rc<std::string::String>: serde::Serialize` is not satisfied,note 让你查「别的 crate」的 feature,其实要开的是 serde 的rc。- 标题里的
serde::Serialize来自diagnostic::on_unimplemented的message(1.0.220,#2963);两条 note 来自 1.0.204 的 #2767。 - 开了
rc之后,每个Rc按内容各写一份,读回来每个计数为 1,Rc<RefCell<_>>的共享可变性丢失,Weak永远是Weak::new()。 - 要保留共享,在线上写编号、转换时自己去重和重建。
- feature 在依赖图里合并:别的依赖开了
rc,你的代码也能编译;用cargo tree -e features -i serde查,并在自己的Cargo.toml里显式写上。