Rust in Everything
基于 serde 1.0.229

the trait bound Post: serde::Serialize is not satisfied(明明 derive 了)

主线位置:不在主线上,卡在序列化主线的入口。serde_json::to_string<T> 要求 T: Serialize,编译期就检查;类型实现的是另一个 crate 里同名的 trait,这道门进不去。

flowchart TB
  app["你的 crate"] --> oldlib["oldlib"]
  app --> json["serde_json 1.0.154"]
  oldlib --> s09["serde 0.9.15"]
  json --> core["serde_core 1.0.229"]
  s09 --> t09["0.9 的 Serialize"]
  core --> t1["1.x 的 Serialize"]
  t09 -.->|"不是同一个 trait"| t1

serde 的四个 crate 怎么锁在一起、为什么 serde_json 要求 serde >= 1.0.220,在 源码地图 的「版本怎么锁在一起」里讲过。这一页讲依赖树里同时有两个 serde 主版本时,报错长什么样、怎么认出来。

现象

一个还没升级的库 oldlib,依赖 serde 0.9,用 0.9 的 derive:

# oldlib/Cargo.toml
[package]
name = "oldlib"
version = "0.1.0"
edition = "2015"
publish = false

[dependencies]
serde = "0.9"
serde_derive = "0.9"
// A library that has not been upgraded: it derives serde 0.9's Serialize.
#[macro_use]
extern crate serde_derive;

#[derive(Serialize, Debug)]
pub struct Post {
  pub title: String,
}

你的 crate 用 serde_json 1.x 序列化它(newlib 是后面修法里用的,这里先不管):

[dependencies]
oldlib = { path = "oldlib" }
newlib = { path = "newlib" }
serde_json = "=1.0.154"
// oldlib::Post implements serde 0.9's Serialize; serde_json 1.x wants serde 1.x's.
fn main() {
  let p = oldlib::Post { title: "hi".into() };
  println!("{}", serde_json::to_string(&p).unwrap());
}

M2 Max、macOS、rustc 1.99.0,cargo build --offline:

error[E0277]: the trait bound `Post: serde::Serialize` is not satisfied
    --> src/main.rs:4:40
     |
   4 |   println!("{}", serde_json::to_string(&p).unwrap());
     |                  --------------------- ^^ the trait `serde_core::ser::Serialize` is not implemented for `Post`
     |                  |
     |                  required by a bound introduced by this call
     |
     = note: for local types consider adding `#[derive(serde::Serialize)]` to your `Post` type
     = note: for types from other crates check whether the crate offers a `serde` feature flag
     = note: `Post` implements similarly named trait `serde::ser::Serialize`, but not `serde_core::ser::Serialize`
     = help: the following other types implement trait `serde_core::ser::Serialize`:
               &'a T
               &'a mut T
               ()
               (T,)
               (T0, T1)
               (T0, T1, T2)
               (T0, T1, T2, T3)
               (T0, T1, T2, T3, T4)
             and 129 others
note: required by a bound in `serde_json::to_string`
    --> ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde_json-1.0.154/src/ser.rs:2247:17
     |
2245 | pub fn to_string<T>(value: &T) -> Result<String>
     |        --------- required by a bound in this function
2246 | where
2247 |     T: ?Sized + Serialize,
     |                 ^^^^^^^^^ required by this bound in `to_string`

For more information about this error, try `rustc --explain E0277`.

第一行和第一条 note 都在劝你「加上 #[derive(serde::Serialize)]」,可 Post 明明 derive 过了。真正的线索是第三条 note:Post 实现了 serde::ser::Serialize(0.9 的),但没实现 serde_core::ser::Serialize(1.x 的)。

编译 oldlib 时还有一个警告,是另一条线索:

warning: non-local `impl` definition, `impl` blocks should be written at the same level as their item
 --> oldlib/src/lib.rs:5:10
  |
5 | #[derive(Serialize, Debug)]
  |          ^^^^^^^^^
  |          |
  |          `Post` is not local
  |          `Serialize` is not local
  |          move the `impl` block outside of this constant `_IMPL_SERIALIZE_FOR_Post`
  |
  = note: the derive macro `Serialize` defines the non-local `impl`, and may need to be changed
  = note: the derive macro `Serialize` may come from an old version of the `serde_derive` crate, try updating your dependency with `cargo update -p serde_derive`
  = note: an `impl` is never scoped, even when it is nested inside an item, as it may impact type checking outside of that item, which can be the case if neither the trait or the self type are at the same nesting level as the `impl`
  = note: items in an anonymous const item (`const _: () = { ... }`) are treated as in the same scope as the anonymous const's declaration for the purpose of this lint
  = note: `#[warn(non_local_definitions)]` on by default
  = note: this warning originates in the derive macro `Serialize` (in Nightly builds, run with -Z macro-backtrace for more info)

为什么

trait 是按定义它的 crate 区分的。serde 0.9.15 和 serde_core 1.0.229 是两个 crate,各自定义了一个叫 Serialize 的 trait,彼此毫无关系。0.9 的 derive 生成的是 0.9 trait 的 impl;serde_json::to_string 的约束 T: ?Sized + Serialize 指的是 serde_core 里的 Serialize。名字一样,类型系统看来是两回事。

cargo 允许这种依赖图:0.9 和 1 是不同的 semver 主版本,两个版本可以同时存在。

报错第一行说 serde::Serialize,而标签和 note 里是 serde_core::ser::Serialize,因为 trait 上有一个自定义的诊断:

#[cfg_attr(
    not(no_diagnostic_namespace),
    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",
    )
)]
pub trait Serialize {

message 换掉了第一行,两条 note 就是那两句「consider adding」「check whether the crate offers a serde feature flag」。它们对最常见的情况(类型真的没 derive、或者第三方 crate 的 serde 支持在 feature 后面)是对的,对这个坑是误导。

同样的依赖放在 serde_core 出现之前,rustc 的说法不一样。把你的 crate 换成 serde = "=1.0.219"、serde_json = "=1.0.140"(这时 Serialize 还定义在 serde 自己里面),同一个 main.rs:

error[E0277]: the trait bound `Post: serde::ser::Serialize` is not satisfied
    --> src/main.rs:4:40
     |
   4 |   println!("{}", serde_json::to_string(&p).unwrap());
     |                  --------------------- ^^ the trait `serde::ser::Serialize` is not implemented for `Post`
     |                  |
     |                  required by a bound introduced by this call
     |
     = note: for local types consider adding `#[derive(serde::Serialize)]` to your `Post` type
     = note: for types from other crates check whether the crate offers a `serde` feature flag
note: there are multiple different versions of crate `serde` in the dependency graph
    --> ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde-1.0.219/src/ser/mod.rs:225:1
     |
 225 | pub trait Serialize {
     | ^^^^^^^^^^^^^^^^^^^ this is the expected trait
     |
    ::: ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde-0.9.15/src/ser/mod.rs:179:1
     |
 179 | pub trait Serialize {
     | ------------------- this is the found trait
     = help: you can use `cargo tree` to explore your dependency tree
     = help: the following other types implement trait `serde::ser::Serialize`:
               &'a T
               &'a mut T
               ()
               (T,)
               (T0, T1)
               (T0, T1, T2)
               (T0, T1, T2, T3)
               (T0, T1, T2, T3, T4)
             and 131 others
note: required by a bound in `serde_json::to_string`
    --> ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde_json-1.0.140/src/ser.rs:2239:17
     |
2237 | pub fn to_string<T>(value: &T) -> Result<String>
     |        --------- required by a bound in this function
2238 | where
2239 |     T: ?Sized + Serialize,
     |                 ^^^^^^^^^ required by this bound in `to_string`

For more information about this error, try `rustc --explain E0277`.

两个 trait 都在叫 serde 的 crate 里时,rustc 能认出「同一个 crate 的两个版本」,直接点名并指向两处定义。1.0.220 起 1.x 的 trait 在 serde_core 里,crate 名不同,rustc 只能退而说「名字相近的 trait」。

📝NOTE
冷知识:serde 本可以让 rustc 继续说「多个版本」,但没这么做

PR #2963「Override diagnostic::on_unimplemented message for all serde_core traits」(首次发布于 1.0.220,和 serde_core 本身同一个版本)加上了上面那个 message。PR 说明里的 diff 显示,不加的话第一行会是 the trait bound `MyStruct: serde_core::ser::Serialize` is not satisfied,对不直接依赖 serde_core 的项目很陌生。PR 说明里提到另一条路:「I explored the alternative of setting [lib] name = "serde" in serde_core/Cargo.toml which also prevents serde_core::ser::Serialize in messages, but has the unwanted effect of also inserting the following noise into every such error」,后面贴的就是 note: there are multiple different versions of crate `serde` in the dependency graph:serde 和 serde_core 若都叫 serde,rustc 会把每一个普通的「忘了 derive」都报成两个版本冲突。所以选了只改 message。代价是这一页的情形里少了那条最直接的 note,只剩「similarly named trait」。

serde 和 serde_derive 版本不配呢

两个 1.x 版本不配(比如 serde = "=1.0.228" 配 serde_derive = "=1.0.229")构造不出这个 E0277:serde_core 用一条永远不编译的 cfg(any()) 依赖把 serde_derive 锁成同一个版本,cargo 在版本求解阶段就停下了。源码地图 里的 exp-serde-intro/mismatch,重跑一次(本地路径缩写成 <scratchpad>):

error: failed to select a version for `serde_derive`.
    ... required by package `serde_core v1.0.228`
    ... which satisfies dependency `serde_core = "=1.0.228"` of package `serde v1.0.228`
    ... which satisfies dependency `serde = "=1.0.228"` of package `exp-serde-intro-mismatch v0.1.0 (<scratchpad>/exp-serde-intro/mismatch)`
versions that meet the requirements `=1.0.228` are: 1.0.228

all possible versions conflict with previously selected packages

  previously selected package `serde_derive v1.0.229`
    ... which satisfies dependency `serde_derive = "=1.0.229"` of package `exp-serde-intro-mismatch v0.1.0 (<scratchpad>/exp-serde-intro/mismatch)`

failed to select a version for `serde_derive` which could resolve this conflict

同理,serde_json 1.0.154 带着一条 cfg(any()) 的 serde >= 1.0.220,serde 1.0.219 和它放不进同一个依赖图(报错也在 源码地图)。上面 1.0.219 那个对照实验之所以能编译到 rustc 这一步,是因为配的是同样没有 serde_core 的 serde_json 1.0.140。所以在 1.x 内部,「两个 Serialize」被挡在了 cargo;能走到 E0277 的,只剩不同主版本(0.x 与 1.x)共存这一种。

修法

让类型实现 serde_json 要的那个 trait:把库升级到 serde 1。升级后的 newlib:

[dependencies]
serde = { version = "1", features = ["derive"] }
// The same library after upgrading to serde 1.
#[derive(serde::Serialize, Debug)]
pub struct Post {
  pub title: String,
}

src/bin/fixed.rs 把 oldlib::Post 换成 newlib::Post,其余不变:

{"title":"hi"}

库不是你的、又一时升不了,就在自己的 crate 里定义一个 serde 1 的类型,从库的类型转换过去再序列化。

怎么确认没中招

  • 看到「明明 derive 了」的 E0277,先找 similarly named trait 或 multiple different versions of crate 这两种 note。

  • cargo tree -d 列出所有出现了不止一个版本的 crate;看 serde 一项。直接查谁拉进了旧版(本地路径缩写成 <scratchpad>):

    $ cargo tree --offline -i serde@0.9.15
    serde v0.9.15
    └── oldlib v0.1.0 (<scratchpad>/exp-serde-pit4/old/oldlib)
        └── exp-serde-pit4-old v0.1.0 (<scratchpad>/exp-serde-pit4/old)
    
  • 编译依赖时出现 non-local impl definition 警告、note 里提到「an old version of the serde_derive crate」,说明有库在用很旧的 derive。

小结

  • serde 0.9 和 1.x 是两个主版本,可以同时在依赖图里;各自的 Serialize 是不相干的 trait,0.9 derive 出来的 impl 对 serde_json 1.x 不存在。
  • 1.0.229 的报错第一行和前两条 note 是 serde_core 的 on_unimplemented 写的,劝你加 derive;真正的线索是 rustc 的 implements similarly named trait serde::ser::Serialize, but not serde_core::ser::Serialize。
  • serde_core 出现之前(1.0.219 + serde_json 1.0.140),rustc 会直接说「multiple different versions of crate serde」。#2963 有意不让 serde_core 叫 serde,以免每个普通错误都带上这条 note。
  • 1.x 内部的版本不配被 cfg(any()) 约束挡在 cargo 解析阶段:failed to select a version。
  • 修法是把库升到 serde 1;查法是 cargo tree -d / cargo tree -i serde@0.9.15。

💬 关联讨论

发起讨论
加载中...