Rust in Everything
基于 serde 1.0.229

源码地图

主线位置:不在主线上。本页说明三条主线经过的代码分在哪几个 crate、哪些文件,它们怎么互相依赖、怎么被锁成同一个版本,以及仓库用哪些测试守住生成代码。

serde 仓库在 v1.0.229 是一个五个成员的 Cargo 工作区:四个发布的 crate,加一个不发布的 test_suite。依赖关系是这样的(虚线是 cfg(any()) 下的依赖,下面解释):

flowchart TB
  user["你的 crate"] --> serde["serde"]
  user -.->|"不开 derive 时"| sd["serde_derive"]
  serde -->|"= 1.0.229"| core["serde_core"]
  serde -->|"可选,开 derive"| sd
  core -.->|"cfg(any()) = 1.0.229"| sd
  json["serde_json"] --> core
  json -.->|"cfg(any()) 至少 1.0.220"| serde
  sd --> syn["syn 3、quote、proc-macro2"]
  sdi["serde_derive_internals"] --> syn

四个 crate

cratev1.0.229 里的版本管什么运行时依赖
serde_core1.0.229全部 trait(ser、de 两个模块)、标准库类型的 impl、de::value、forward_to_deserialize_any!;private 里几样共用的小东西(Content 的定义、size_hint、InPlaceSeed)无
serde1.0.229重新导出 serde_core 的公开 API;private/de.rs 和 private/ser.rs 是 derive 生成的代码要调的运行时(ContentDeserializer、FlatMapDeserializer、missing_field…);开 derive 时重新导出两个派生宏serde_core、可选的 serde_derive
serde_derive1.0.229过程宏 #[derive(Serialize)]、#[derive(Deserialize)]proc-macro2、quote、syn 3
serde_derive_internals0.29.1把 serde_derive 的属性解析和 AST(internals/)单独发布出来,Cargo.toml 的描述是「AST representation used by Serde derive macros. Unstable.」proc-macro2、quote、syn 3

serde_derive_internals 没有自己的源码:它的 src 是一个指向 ../serde_derive/src/internals 的符号链接,lib.rs 只有一行 mod internals; 加 pub use internals::*;(Windows 上符号链接失效时,build.rs 设 serde_build_from_git,改用相对路径找同一个目录)。serde_derive 并不依赖它,而是把 internals/ 当成自己的模块直接编译。它也不跟 serde 同步发版:Cargo.toml 里还是 0.29.1,这个版本号是 2024-05-15 的提交「Release serde_derive_internals 0.29.1」定下的,之后 internals/ 又改了很多次(1.0.229 的 syn 3 也在其中),但版本号没有动。

在本卷的实验 crate 里跑 cargo tree,依赖 serde = { version = "=1.0.229", features = ["derive", "rc"] }、serde_json = "=1.0.154" 等(M2 Max、macOS、rustc 1.99.0):

serde v1.0.229
├── serde_core v1.0.229
└── serde_derive v1.0.229 (proc-macro)
    ├── proc-macro2 v1.0.107
    │   └── unicode-ident v1.0.27
    ├── quote v1.0.47
    │   └── proc-macro2 v1.0.107 (*)
    └── syn v3.0.9
        ├── proc-macro2 v1.0.107 (*)
        ├── quote v1.0.47 (*)
        └── unicode-ident v1.0.27

反过来看谁直接依赖 serde_core(cargo tree -i serde_core -e normal --depth 1):

serde_core v1.0.229
├── serde v1.0.229
├── serde_json v1.0.154
└── serde_with v3.24.0

serde_json 不依赖 serde,只依赖 serde_core。这是 1.0.220 拆分的目的,下面单独讲。

版本怎么锁在一起

serde_derive 生成的代码要调 serde 里的私有 API(_serde::__private229::de::missing_field 之类),这些 API 不受 semver 约束,每个版本都可能变。所以 serde_derive 只能和同一个版本的 serde 一起用。serde/Cargo.toml 的依赖是:

[dependencies]
serde_core = { version = "=1.0.229", path = "../serde_core", default-features = false, features = ["result"] }
serde_derive = { version = "1", optional = true, path = "../serde_derive" }

serde 对 serde_core 用 = 锁死,对 serde_derive 却只写了 "1"。锁 serde_derive 的那一行在 serde_core/Cargo.toml 里:

# This cfg cannot be enabled, but it still forces Cargo to keep serde_derive's
# version in lockstep with serde's, even if someone depends on the two crates
# separately with serde's "derive" feature disabled. Every serde_derive release
# is compatible with exactly one serde release because the generated code
# involves nonpublic APIs which are not bound by semver.
[target.'cfg(any())'.dependencies]
serde_derive = { version = "=1.0.229", path = "../serde_derive" }

cfg(any()) 是一个永远为假的条件(空的 any),所以这个依赖永远不会被编译、不会拖慢构建;但 Cargo 解析依赖时不看 cfg 真假,照样把 =1.0.229 这条约束放进版本求解。于是链条是:serde = serde_core 的版本,serde_core = serde_derive 的版本。不管用户写 serde = { features = ["derive"] },还是分开写 serde = "1" 和 serde_derive = "1",同一个依赖图里三者都只能是同一个版本。

这个写法来自「guarantee serde is in lockstep with serde_derive」(#2588,1.0.186,发布说明:「Disallow incompatible versions of serde_derive and serde in the dependency graph」),当时放在 serde/Cargo.toml 里。1.0.220 拆出 serde_core 时,提交「Relocate serde_derive version constraint from serde to serde_core」(#2966)把它挪到了 serde_core,提交说明是:

This disallows using an old version of serde_derive against a new
version of serde_core (with a rename to serde in Cargo.toml).

故意写一个不配对的依赖试试:

[dependencies]
serde = "=1.0.228"
serde_derive = "=1.0.229"

cargo build --offline 在版本求解这一步就失败了(crate 的本地路径缩写成 .../):

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 (.../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 (.../exp-serde-intro/mismatch)`

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

报错里的链条正是上面说的:serde 1.0.228 要 serde_core =1.0.228,serde_core 1.0.228 要 serde_derive =1.0.228,和用户要的 =1.0.229 冲突。

serde_json 用的是同一个技巧,方向相反。serde_json 的 Cargo.toml 依赖 serde_core = { version = "1.0.220", default-features = false },再加一条:

[target.'cfg(any())'.dependencies]
serde = { version = "1.0.220", default-features = false }

这条在 1.0.145 加入(提交「Add serde version constraint」,发布说明「Raise serde version requirement to >=1.0.220」),上一个版本 1.0.144 刚把依赖从 serde 换成 serde_core。用 serde = "=1.0.219" 加 serde_json = "=1.0.154" 试一下:

error: failed to select a version for `serde`.
    ... required by package `serde_json v1.0.154`
    ... which satisfies dependency `serde_json = "=1.0.154"` of package `exp-serde-intro-old-serde v0.1.0 (.../exp-serde-intro/old-serde)`
versions that meet the requirements `^1.0.220` are: 1.0.229, 1.0.228

all possible versions conflict with previously selected packages

  previously selected package `serde v1.0.219`
    ... which satisfies dependency `serde = "=1.0.219"` of package `exp-serde-intro-old-serde v0.1.0 (.../exp-serde-intro/old-serde)`

failed to select a version for `serde` which could resolve this conflict
📝NOTE
没有蠢问题

问: serde_json 的 to_string 要的是 T: serde_core::Serialize,我的类型 derive 的是 serde::Serialize。这是两个 trait 吗?serde_json 为什么非要拦住 1.0.219?

答: 从 1.0.220 起是同一个 trait:serde 里没有自己的 Serialize,只有一行 pub use serde_core::{..., Serialize, ...},serde::Serialize 和 serde_core::Serialize 是同一个定义的两个名字。1.0.219 还没有 serde_core,它的 Serialize 定义在 serde 自己的源码里。如果允许 serde 1.0.219 和依赖 serde_core 的 serde_json 共存,依赖图里就会有两个不相干的 Serialize trait,derive 出来的 impl 对 serde_json 来说等于不存在,而报错里两个 trait 都叫 Serialize,很难看懂。那条 cfg(any()) 约束把这种组合挡在了版本求解阶段。

serde/src/lib.rs:一层重新导出

serde/src/ 一共 5 个文件。lib.rs 的主体是一个 crate_root! 宏,展开后得到这些东西(摘关键几行):

#[cfg(not(docsrs))]
macro_rules! crate_root {
    () => {
        /// A facade around all the types we need from the `std`, `core`, and `alloc`
        /// crates. This avoids elaborate import wrangling having to happen in every
        /// module.
        mod lib {
            // ...
        }

        // ...
        #[cfg(not(no_serde_derive))]
        macro_rules! tri {
            // ...
        }

        ////////////////////////////////////////////////////////////////////////////////

        pub use serde_core::{
            de, forward_to_deserialize_any, ser, Deserialize, Deserializer, Serialize, Serializer,
        };

        // Used by generated code and doc tests. Not public API.
        #[doc(hidden)]
        mod private;

        include!(concat!(env!("OUT_DIR"), "/private.rs"));
    };
}

公开 API 全部来自 serde_core:de、ser 两个模块,四个 trait,一个宏。serde 自己只多了两样:mod private(private/de.rs 3 501 行、private/ser.rs 1 382 行,derive 生成的代码要调的运行时),以及 include! 进来的 __private229 模块,见后面一节。

派生宏的重新导出在文件后部:

// Re-export #[derive(Serialize, Deserialize)].
//
// The reason re-exporting is not enabled by default is that disabling it would
// be annoying for crates that provide handwritten impls or data formats. They
// would need to disable default features and then explicitly re-enable std.
#[cfg(feature = "serde_derive")]
extern crate serde_derive;

/// Derive macro available if serde is built with `features = ["derive"]`.
#[cfg(feature = "serde_derive")]
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
pub use serde_derive::{Deserialize, Serialize};

Serialize 这个名字同时是一个 trait 和一个派生宏,它们在不同的命名空间里,use serde::Serialize 一次把两个都带进来。

为什么文档构建时要把 serde_core 内联进来

lib.rs 里 crate_root! 宏前面还有一段只在 docsrs 下生效的代码,注释是:

// Rustdoc has a lot of shortcomings related to cross-crate re-exports that make
// the rendered documentation of serde_core traits in serde more challenging to
// understand than the equivalent documentation of the same items in serde_core.
// https://github.com/rust-lang/rust/labels/A-cross-crate-reexports
// So, just for the purpose of docs.rs documentation, we inline the contents of
// serde_core into serde. This sidesteps all the cross-crate rustdoc bugs.
#[cfg(docsrs)]
#[macro_use]
#[path = "core/crate_root.rs"]
mod crate_root;

serde/src/core 是一个指向 ../../serde_core/src 的符号链接。构建文档时,serde 不用上面那个只做 pub use 的 crate_root!,而是把 serde_core 的 crate_root.rs 当成自己的模块编译,serde 的文档里于是是一份「原生」的 trait 文档,而不是跨 crate 的重新导出。这是拆分之后补上的:「Inline serde_core into serde in docsrs mode」(#2991,1.0.227),PR 说明拆分(#2608)让 serde 的文档在很多地方变差了:

#2608 regressed the rustdoc rendered documentation of `serde` in many ways because rustdoc does not currently handle documentation of cross-crate re-exported items well. For example there is not enough information in the rmeta of dependencies to accurately reconstruct the way that trait associated types are named in signatures.

serde_core/src/crate_root.rs 里的模块因此都带着形如 #[cfg_attr(all(docsrs, if_docsrs_then_no_serde_core), path = "core/de/mod.rs")] 的属性:被 serde 内联时,路径要多一层 core/。if_docsrs_then_no_serde_core 由 serde/build.rs 无条件地设上,serde_core 自己编译时没有它。于是 core/ 前缀只在「docsrs 且正在被内联进 serde」时成立。1.0.227 里这个条件是另一个 cfg no_serde_core,只在 docs.rs 的环境变量 DOCS_RS 存在时才设。按 diff 看,自己设 RUSTDOCFLAGS='--cfg=docsrs' 构建文档时,serde 走了 #[cfg(docsrs)] 的内联分支,路径却没有加 core/。有人报告 1.0.227 下这样构建文档失败、1.0.226 正常(#2994),1.0.228 的「Workaround for RUSTDOCFLAGS='--cfg=docsrs'」(#2995)换成了现在的写法。

serde_core:trait 都在这里

serde_core/src/lib.rs 也只是调用 crate_root!(),宏定义在 crate_root.rs:

  • mod lib:把 core、alloc、std 里要用的东西按 feature 收拢成一个门面,其余代码一律 use crate::lib::*,不用在每个文件里写 #[cfg(feature = "std")]。
  • tri!:代替 ? 的宏(为什么见怎么读这一卷)。
  • pub mod de、pub mod ser:全部 trait、标准库类型的 impl、de::value。
  • mod private 和 pub mod __private:后者只放了 doc 和 Result,注释是「Used by declarative macro generated code」,即 forward_to_deserialize_any! 这类声明宏展开后要用的路径。
  • 同样 include! 一个 __private229,指向 serde_core 自己的 private。

Content 的定义放在 serde_core/src/private/content.rs,而不是和用它的 ContentDeserializer 一起放在 serde,原因是 Deserializer trait 上有一个隐藏方法 __deserialize_content_v1,它的签名里写着 Visitor<'de, Value = crate::private::Content<'de>>:trait 在 serde_core,签名里出现的类型也只能在 serde_core。这个方法是做什么的,见 Content:缓冲的代价。

1.0.220:拆出 serde_core

拆分来自「feat: Extract serde_core out of serde crate」(#2608,提交 f3869307),2025-09-13 合并,首次随 1.0.220 发布,发布说明是:

- Add a way for data formats to depend on serde traits without waiting for serde_derive compilation: <https://docs.rs/serde_core> (#2608, thanks @osiewicz)

PR 说它实现的是 #2584 里提出的办法。那个 issue 把问题讲得很具体:开着 derive 的 serde 会造出一条不能并行的依赖链:

`proc-macro2` compile build script > `proc-macro2` run build script > `proc-macro2` > `quote` > `syn` > `serde_derive` > `serde` > `serde_json` (or any crate that depends on serde)

serde 要等 serde_derive 编完才开始编,依赖 serde 的格式 crate 又要等 serde。拆分之后,trait 在不依赖任何过程宏的 serde_core 里,serde_json 这样的格式 crate 只依赖 serde_core,可以和 syn → serde_derive 这条链同时编译。PR 给的数字是:开 derive 的 release 全量构建,8 核,从 11.4 秒降到 7.8 秒。serde_core 的 crate 文档写着同一个结论:

Crates that depend on `serde_core` instead of `serde` are able to compile in
parallel with `serde_derive` even when `serde`'s "derive" feature is turned on,
as shown in the following build timings.

PR 说明里还提到两处改了又撤回的地方,其中一处是「I've changed serde_derive to use serde_core instead of serde. This has since been reverted as it would be a breaking change.」:生成的代码至今写的是 extern crate serde as _serde,所以用 derive 的 crate 必须依赖 serde,只有手写 impl 或只拿 trait 当 bound 的 crate 才可以改依赖 serde_core。serde_core 的 crate 文档就是这么说的:「In crates that derive an implementation of Serialize or Deserialize, you must depend on the [serde] crate, not serde_core.」

这条规则是强制的。serde_derive 的 dummy.rs 在每个生成的 const _: () = { ... } 开头插一行 _serde::__require_serde_not_serde_core!();。这个宏两个 crate 都有:serde 里的版本展开成空,serde_core 里的版本展开成 compile_error!("Serde derive requires a dependency on the serde crate, not serde_core")。它来自 #2966 的另一个提交「Enforce derive cannot be used against serde_core」,提交说明解释了为什么连只用到公开 trait 的简单类型也要拦:

Even for a simple data structure that would ordinarily expand to an impl
that only refers to the public Serde traits without serde::__private, we
still disallow a (renamed) serde_core dependency. This leaves the
liberty to new usage of private helpers in such impls over time.
❗IMPORTANT
动脑筋:把 serde_core 改名成 serde,derive 还能用吗?
[dependencies]
serde = { package = "serde_core", version = "=1.0.229" }
serde_derive = "=1.0.229"
use serde_derive::Serialize;

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

fn main() {
  let _ = Post { title: String::new() }.title;
}

extern crate serde as _serde 找到的是改了名的 serde_core。M2 Max、macOS、rustc 1.99.0 上 cargo build,能编译通过吗?不能的话,会报几个错?

feature

serde/Cargo.toml 和 serde_core/Cargo.toml 的 feature:

featureserdeserde_core打开了什么
std默认开,转给 serde_core/std默认开标准库类型的 impl(HashMap、Path、SystemTime、Mutex…);不开时 #![no_std]
alloc转给 serde_core/alloc否不带 std 时只要 alloc 里的类型:String、Vec、Box、Cow、BTreeMap…
rc转给 serde_core/rc否Rc<T>、Arc<T> 和它们的 Weak。注释:「Serializing and deserializing these types does not preserve identity and may result in multiple copies of the same data.」
unstable转给 serde_core/unstable否nightly 上的 !:impl Serialize for !、impl Deserialize for !(报「cannot deserialize !」)、NeverDeserializer
result依赖时总是打开默认开Result<T, E> 的 impl。注释:「Convenient in some contexts but can lead to confusion if ? or unwrap are used incautiously.」
derive否—打开可选依赖 serde_derive,重新导出两个派生宏

另外 serde_derive 自己有一个 deserialize_in_place feature,打开后生成的 Deserialize 多一个 deserialize_in_place 方法,test_suite 测试时会打开它。

几点要注意:

  • result 只在 serde_core 上有,是 1.0.220 拆分时加的(提交「review: Gate Result impls behind a feature gate」)。serde 依赖 serde_core 时写死了 features = ["result"],所以对 serde 的用户来说 Result 的 impl 一直都在,和拆分前一样。直接依赖 serde_core 时才能选择不要它。serde_json 依赖 serde_core 时 default-features = false,自己的 std feature 只转发 serde_core/std,不打开 result。
  • derive 不是默认 feature。上面那段注释给了理由:手写 impl 的 crate 和格式 crate 不需要它,要是默认打开,它们就得 default-features = false 再手动把 std 开回来。
  • feature 在整个依赖图里是合并的:依赖图里只要有一个 crate 给 serde 开了 rc,所有人都能用 Rc 的 impl。

__private229:带补丁号的私有模块

生成的代码里,所有私有路径都写成 _serde::__private229::...。这个模块不在任何源文件里,是 serde/build.rs 生成的:

const PRIVATE: &str = "\
#[doc(hidden)]
pub mod __private$$ {
    #[doc(hidden)]
    pub use crate::private::*;
}
use serde_core::__private$$ as serde_core_private;
";

// ...
fn main() {
    // ...
    let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
    let patch_version = env::var("CARGO_PKG_VERSION_PATCH").unwrap();
    let module = PRIVATE.replace("$$", &patch_version);
    fs::write(out_dir.join("private.rs"), module).unwrap();

CARGO_PKG_VERSION_PATCH 是 Cargo 给构建脚本的补丁版本号,1.0.229 就是 229。lib.rs 里那行 include!(concat!(env!("OUT_DIR"), "/private.rs")) 把生成的文件包含进来。本卷实验 crate 的 target/debug/build/serde-*/out/private.rs 的真实内容:

#[doc(hidden)]
pub mod __private229 {
    #[doc(hidden)]
    pub use crate::private::*;
}
use serde_core::__private229 as serde_core_private;

serde_core/build.rs 生成同样的 __private229(少最后那行 use),serde 通过 serde_core_private 这个别名取用 serde_core 的私有部分,比如 private/de.rs 里的 pub use crate::serde_core_private::Content;。

生成代码的那一侧在 serde_derive/src/lib.rs:

#[allow(non_camel_case_types)]
struct private;

impl private {
    fn ident(&self) -> Ident {
        Ident::new(
            concat!("__private", env!("CARGO_PKG_VERSION_PATCH")),
            Span::call_site(),
        )
    }
}

impl ToTokens for private {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        tokens.append(self.ident());
    }
}

private 是一个实现了 ToTokens 的单元结构体,quote! 里写 _serde::#private::de::missing_field,展开时就拼出 _serde::__private229::de::missing_field。名字是在编译 serde_derive 时由 env! 定下的,用的是 serde_derive 自己的补丁号;版本锁保证它和 serde 的补丁号相同。

__private229 是 pub 的(加了 #[doc(hidden)]),任何 crate 都能写 serde::__private229::de::Content。它防的正是这件事:用了就会在下一个补丁版本编译失败。

📝NOTE
冷知识:模块名为什么要带补丁号

到 1.0.224 为止,这个模块就叫 serde::__private(v1.0.224 的 lib.rs:#[path = "private/mod.rs"] pub mod __private;)。不止生成的代码在用它:有人报告(#2982)升级 serde_derive 到 1.0.224 后 CI 里 lambda_http 0.8.3 编译失败,那个 crate 直接调用了 serde 的私有类型:

error[E0599]: no variant or associated item named `deserialize` found for enum `serde_core::__private::content::Content` in the current scope

这个 issue 是在「Use differently named __private module per patch release」(#2980,首次随 1.0.225 发布)合并 20 分钟后提的,那个 PR 当天已经把模块名改成了带补丁号的形式(1.0.225 里叫 __private225),就是上面 build.rs 和 serde_derive 里的写法。PR 没有写说明,讨论里有人问这是不是为了防住随手用私有类型的人,维护者的回答是:

I think it is low cost and decent benefit. If there is any logic in there that someone wants access to, they need to find someone to maintain it officially so that their users do not experience repeated breakage from serde updates.

之前,下游 crate 用了 __private 里的东西,平时能编译,等到某个版本恰好改了那个类型才坏;之后,每个补丁版本都换名字,用了私有模块的代码在下一次升级时一定编译失败,问题在第一次就暴露出来。derive 生成的代码不受影响,因为 serde_derive 和 serde 永远同版本、同名字。

test_suite/

test_suite 是工作区成员,包名 serde_test_suite,publish = false。它不依赖任何格式 crate,靠 serde_test(1.0.176)把序列化结果表示成一串 Token:

use serde_test::{assert_tokens, Configure, Token};
use std::net;

#[macro_use]
#[allow(unused_macros)]
mod macros;

#[test]
fn ip_addr_roundtrip() {
    assert_tokens(
        &net::IpAddr::from(*b"1234").compact(),
        &seq![
            Token::NewtypeVariant {
                name: "IpAddr",
                variant: "V4"
            },
            Token::Tuple { len: 4 },
            b"1234".iter().copied().map(Token::U8),
            Token::TupleEnd,
        ],
    );
}

assert_tokens 两个方向都查:值序列化出来的调用序列(serialize_newtype_variant、serialize_tuple(4)、四次 serialize_u8、end)必须是这串 token,把这串 token 当输入反序列化又必须得到原值。测试直接对着数据模型写,不经过 JSON,所以测到的就是 Serialize / Deserialize impl 本身。

tests/ 下的文件(v1.0.229,共 14 216 行、410 个 #[test]):

文件行数#[test]测什么
test_annotations.rs3 57661各种 #[serde(...)] 属性:default、skip_*、rename、flatten、with…
test_de.rs2 38798标准库类型和 derive 类型的反序列化
test_de_error.rs1 56866反序列化的报错原文
test_enum_internally_tagged.rs1 47824内部标签 enum
test_gen.rs9601只要求编译通过:「These just test that serde_derive is able to produce code that compiles successfully when there are a variety of generics and non-(de)serializable types involved.」
test_ser.rs91963序列化
test_macros.rs86627derive 的基本形态:struct、enum、泛型
test_enum_adjacently_tagged.rs79919相邻标签 enum
test_enum_untagged.rs58319untagged enum
test_remote.rs2630#[serde(remote = "...")],只要求编译通过
test_borrow.rs19612'de 借用与 #[serde(borrow)]
test_identifier.rs1889variant_identifier / field_identifier
其余 9 个共 43311test_ignored_any、test_value、test_roundtrip、test_self、test_serde_path、test_unstable、test_deprecated、compiletest、regression

还有几个子目录:

  • tests/regression/:8 个以 issue 号命名的文件(issue1904.rs … issue2846.rs),由 regression.rs 里的 automod::dir!("tests/regression") 自动收进来,加文件不用改代码。
  • tests/ui/:21 个子目录、118 个 .rs,每个配一个 .stderr,由 compiletest.rs 交给 trybuild 的 compile_fail:每个文件必须编译失败,而且报错要和 .stderr 一字不差。只在 nightly 上跑(#[rustversion::attr(not(nightly), ignore = "requires nightly")]),因为 rustc 的报错格式会随版本变。按子目录分:default-attribute 15 个、with-variant 11 个、identifier 和 remote 各 10 个……例如 duplicate-attribute/rename-and-ser.rs 写了 #[serde(rename = "x", serialize = "y")],对应的 .stderr 是 error: unknown serde field attribute serialize``。derive 的属性检查(属性、派生宏的流水线里的 check.rs)主要靠这些用例守住。
  • no_std/:一个独立的小工作区,#![no_std]、#![no_main]、panic = "abort",依赖 serde(default-features = false)和 serde_derive,证明不带 std 时 derive 出来的代码也能编译。

CI(.github/workflows/ci.yml)在 nightly 上 cd test_suite && cargo test --features unstable,Windows 上跳过 ui 测试;另有任务在 1.71.0 上 cargo check serde 和 serde_derive,在一组老编译器上只构建 serde(--features rc、--no-default-features),serde_core 的测试还在 Miri 下跑一遍。

主要文件的行数

v1.0.229,含文档注释和测试:

crate文件行数本卷
serde_core(19 个文件,12 037 行)de/impls.rs3 173标准库类型的实现
de/mod.rs2 392Deserialize、Deserializer 与 Visitor、三个 Access trait、错误
ser/mod.rs2 010Serialize 与 Serializer
de/value.rs1 895value 模块与 IntoDeserializer
ser/impls.rs1 045标准库类型的实现
de/ignored_any.rs、macros.rs、ser/impossible.rs、crate_root.rs、ser/fmt.rs238、230、216、171、170各章引用
private/(6 个文件)298content.rs 只有 39 行,就是 Content 的定义
serde(5 个文件,5 200 行)private/de.rs3 50104 enum 的表示与缓冲、生成的 Deserialize
private/ser.rs1 382flatten、内部标签与相邻标签
lib.rs285本页
serde_derive(28 个文件,8 975 行)internals/(10 个文件)3 308attr.rs 1 818 行,见属性;check.rs 477 行
de.rs 和 de/(9 个文件)3 359生成的 Deserialize;de/struct_.rs 697、de/identifier.rs 477
ser.rs1 369生成的 Serialize
bound.rs431泛型与 bound
pretend.rs、lib.rs、fragment.rs、dummy.rs 等508派生宏的流水线
serde_json(37 个文件,18 387 行)de.rs、read.rs2 714、1 140反序列化一侧
ser.rs2 285序列化一侧
value/(6 个文件)、map.rs、number.rs、raw.rs4 257、1 189、814、827Value
lexical/(19 个文件)3 707浮点数解析,本卷不讲

两条主线在 serde 一侧经过的,主要是 serde_core 的 ser/mod.rs、de/mod.rs 两个文件里的 trait 定义,加上 serde_derive 的 ser.rs、de/struct_.rs、de/identifier.rs 生成的代码;缓冲主线再加上 serde/src/private/de.rs。serde_core 里分量最重的 de/impls.rs 是标准库类型的 impl,不在主线上。

动脑筋答案

不能编译,只报 1 个错(cargo build --offline,M2 Max、macOS、rustc 1.99.0,Compiling 行省略):

error: Serde derive requires a dependency on the serde crate, not serde_core
 --> src/main.rs:3:10
  |
3 | #[derive(Serialize)]
  |          ^^^^^^^^^
  |
  = note: this error originates in the macro `_serde::__require_serde_not_serde_core` which comes from the expansion of the derive macro `Serialize` (in Nightly builds, run with -Z macro-backtrace for more info)

error: could not compile `exp-serde-intro-core-only` (bin "exp-serde-intro-core-only") due to 1 previous error
  • 报错来自 serde_core 里那个 __require_serde_not_serde_core!,note 里写明了它是 derive 展开出来的。
  • 只有这一个错。这个 struct 的 Serialize impl 用到的私有路径只有 _serde::__private229::Result,而 serde_core 自己也有一个 __private229,里面正好重新导出了 Result,所以除了这一行检查,其余代码其实都能编译。这就是 #2966 的提交说明里「Even for a simple data structure ... we still disallow」的情形:不靠这行检查,这种依赖方式今天能用,等哪天生成代码用上了 serde 独有的私有函数才会坏。

小结

  • 工作区四个发布的 crate:serde_core 放全部 trait 和标准库类型的 impl;serde 重新导出它,再加上 derive 运行时(private/de.rs 3.5k 行、private/ser.rs 1.4k 行)和两个派生宏;serde_derive 是过程宏,依赖 syn 3;serde_derive_internals 是 serde_derive/src/internals 的符号链接,版本停在 0.29.1,不随 serde 发版。
  • 三个 crate 锁成同一版本:serde 依赖 serde_core =1.0.229,serde_core 用永远不编译的 [target.'cfg(any())'.dependencies] 依赖 serde_derive =1.0.229(#2588 引入,#2966 挪到 serde_core)。版本不配对时 cargo 在求解阶段就报错。serde_json 用同一招要求 serde >= 1.0.220,保证依赖图里只有一个 Serialize trait。
  • 1.0.220 拆出 serde_core(#2608)是为了编译并行:格式 crate 不再排在 syn → serde_derive 后面,PR 实测 11.4 秒降到 7.8 秒。derive 仍然要求依赖 serde,__require_serde_not_serde_core! 让改名的 serde_core 直接编译失败。
  • feature:std(默认)、alloc、rc、unstable、derive;serde_core 另有 result,serde 总是打开它。
  • 私有模块由 build.rs 按 CARGO_PKG_VERSION_PATCH 生成,叫 __private229;serde_derive 用同一个环境变量拼出路径。1.0.225 起这样做(#2980),让依赖私有 API 的下游 crate 在每次升级时都立即编译失败。
  • test_suite 用 serde_test 的 Token 序列直接测数据模型层面的调用,410 个 #[test];tests/ui 的 118 个 trybuild 用例在 nightly 上锁住 derive 的报错原文;no_std/ 证明不带 std 也能 derive。

💬 关联讨论

发起讨论
加载中...