Rust in Everything
基于 serde 1.0.229

value 模块与 IntoDeserializer

主线位置:反序列化主线上的「迷你格式」。主线里格式是 serde_json,输入是一段 JSON 文本;这一页的格式是一个已经在内存里的 Rust 值(一个 &str、一个 u32、一个 Vec、一串键值对)。de/value.rs 为每种值写了一个最小的 Deserializer:它把值交给 Visitor,别的什么也不做。

flowchart TB
  s["&str On"] --> into[into_deserializer]
  into --> sd[StrDeserializer]
  sd --> de["Setting::deserialize"]
  de -->|deserialize_enum| ve["visit_enum(self)"]
  ve --> vs[variant_seed]
  vs -->|visit_str| field["__Field On"]
  vs --> uo[UnitOnly]
  uo --> unit[unit_variant]
  unit --> done["Ok(Setting::On)"]

一个值就是一份输入

什么时候需要「把一个 Rust 值当输入再反序列化一次」?手上的数据已经拆好了,但想复用某个类型的 Deserialize:命令行参数里的 "On" 要变成一个 enum,环境变量的键值对要变成一个配置结构体,一个格式自己的 MapAccess 里要把某个值交给下一层。这些场合不需要解析,只需要一个能把值递给 Visitor 的 Deserializer。

整数、bool、char、浮点的那一批由宏 primitive_deserializer! 生成,核心只有这几行:

        impl<'de, E> de::Deserializer<'de> for $name<E>
        where
            E: de::Error,
        {
            type Error = E;

            forward_to_deserialize_any! {
                bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str
                string bytes byte_buf option unit unit_struct newtype_struct seq
                tuple tuple_struct map struct enum identifier ignored_any
            }

            fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
            where
                V: de::Visitor<'de>,
            {
                visitor.$method(self.value $($cast)*)
            }
        }

forward_to_deserialize_any! 把所有 deserialize_* 都转给 deserialize_any(这个宏见 Deserialize、Deserializer 与 Visitor),deserialize_any 调一次和值类型对应的 visit_*。也就是说,这些 Deserializer 不看类型提示:目标类型要的是 u16,U32Deserializer 照样调 visit_u32,转换由目标类型的 Visitor 负责,规则和标准库类型的实现里一样,越界报 invalid value。

整个模块的类型:

类型从什么来调什么
BoolDeserializer、I8Deserializer … F64Deserializer、CharDeserializer对应的基本类型同名 visit_*(isize / usize 转成 visit_i64 / visit_u64)
U32Deserializeru32visit_u32;另外实现了 deserialize_enum,按下标选 unit 变体
StrDeserializer&'a strvisit_str;deserialize_enum 按名字选 unit 变体
BorrowedStrDeserializer&'de str(只能 new)visit_borrowed_str;同上
StringDeserializer、CowStrDeserializerString、Cow<str>visit_string(Cow::Borrowed 调 visit_str);同上
BytesDeserializer、BorrowedBytesDeserializer&[u8]visit_bytes / visit_borrowed_bytes
UnitDeserializer、NeverDeserializer()、!visit_unit(deserialize_option 调 visit_none);后者不可能被构造
SeqDeserializer任意迭代器;Vec、BTreeSet、HashSetvisit_seq,元素逐个 into_deserializer()
MapDeserializer产出 (K, V) 的迭代器;BTreeMap、HashMapvisit_map;deserialize_seq 时把每对当成二元组
SeqAccessDeserializer、MapAccessDeserializer、EnumAccessDeserializer一个现成的 SeqAccess / MapAccess / EnumAccessvisit_seq / visit_map / visit_enum

最后一行是适配器:它们不持有值,持有的是另一个格式交来的访问器,作用是把「已经进了 visit_map 的半路」重新包装成一个 Deserializer,交给另一个类型的 Deserialize。后面有例子。

IntoDeserializer:值怎么变成 Deserializer

IntoDeserializer 在 de/mod.rs 里:

pub trait IntoDeserializer<'de, E: Error = value::Error> {
    /// The type of the deserializer being converted into.
    type Deserializer: Deserializer<'de, Error = E>;

    /// Convert this value into a deserializer.
    fn into_deserializer(self) -> Self::Deserializer;
}

上表左列的每种值都实现了它,5u8.into_deserializer() 得到 U8Deserializer<E>,vec![1, 2].into_deserializer() 得到 SeqDeserializer<vec::IntoIter<i32>, E>。SeqDeserializer 和 MapDeserializer 对元素的要求也只是「元素实现 IntoDeserializer」:next_element_seed 里取出下一个元素,调 value.into_deserializer(),再交给 seed。所以 Vec<Vec<u8>>、BTreeMap<String, Vec<String>> 都能一层层包下去。

错误类型 E 是参数,默认是 value::Error。做成参数,是因为这些小 Deserializer 经常在另一个格式内部使用,报错要变成那个格式的错误类型。serde_json 的 Value 收到 i128 时就是这样,visit_i128 用 serde 的 I128Deserializer 把它转交给 Number 的 Deserialize,错误类型就是外层传进来的 E:

            fn visit_i128<E>(self, value: i128) -> Result<Value, E>
            where
                E: serde::de::Error,
            {
                let de = serde::de::value::I128Deserializer::new(value);
                Number::deserialize(de).map(Value::Number)
            }

单独用的时候没有外层格式,就用默认的 value::Error。实验里因为 E 推不出来,要写成 IntoDeserializer::<value::Error>::into_deserializer(x),或者像下面的 FromStr 那样由返回类型定下来。

每个 XxxDeserializer 自己也实现了 IntoDeserializer,type Deserializer = Self,into_deserializer 原样返回。这让「已经包好的 Deserializer」也能放进 SeqDeserializer / MapDeserializer 当元素,下面的借用例子就靠它。

📝NOTE
冷知识:那些「原样返回自己」的 impl 是 1.0.214 才补齐的

issue #2498(「Consider implementing IntoDeserializer for serde's IntoDeserializer impls」)给的例子正是 impl IntoDeserializer for BorrowedStrDeserializer { type Deserializer = Self; ... }:没有它,想把一串 BorrowedStrDeserializer 交给 MapDeserializer,得先自己定义一个 struct BorrowedStr<'de>(&'de str) 包一层。PR #2568(「Implement IntoDeserializer for all Deserializers in serde::de::value module」,首次发布于 1.0.214)给模块里的每个 Deserializer 都加了这样一个 impl。PR 说明解释了为什么不干脆写一个对所有 Deserializer 的通用 impl:「blanket implementation IntoDeserializer for Deserializer is impossible right now because this would be a breaking change. External crates may have this such implementation (and serde_json actually have it for Value).」serde_json 的 impl IntoDeserializer for Value 就是这种手写的 type Deserializer = Self。

用法一:从 &str 读 enum

模块文档开头的例子,给一个 derive 了 Deserialize 的 enum 实现 FromStr:

#[derive(Deserialize, Debug)]
enum Setting {
  On,
  Off,
}

impl FromStr for Setting {
  type Err = value::Error;

  fn from_str(s: &str) -> Result<Self, Self::Err> {
    Self::deserialize(s.into_deserializer())
  }
}

Setting::deserialize 调 deserialize_enum。StrDeserializer 没有把它转给 deserialize_any,而是自己实现了:visitor.visit_enum(self),把自己当成 EnumAccess。variant_seed 用自己(visit_str)去读变体名,返回的 VariantAccess 是 UnitOnly:unit_variant 返回 Ok(()),newtype_variant_seed / tuple_variant / struct_variant 都报 invalid_type(Unexpected::UnitVariant, ..)。一个字符串里只有变体名,没有内容,只能表示 unit 变体。

  println!("\"On\".parse()    = {:?}", "On".parse::<Setting>());
  println!("\"Maybe\".parse() = {:?}", "Maybe".parse::<Setting>());
  let e = "Maybe".parse::<Setting>().unwrap_err();
  println!("Display: {e}");

  // enum from variant index
  let by_index = Setting::deserialize(IntoDeserializer::<value::Error>::into_deserializer(1u32));
  println!("1u32 -> {by_index:?}");
"On".parse()    = Ok(On)
"Maybe".parse() = Err(Error("unknown variant `Maybe`, expected `On` or `Off`"))
Display: unknown variant `Maybe`, expected `On` or `Off`
1u32 -> Ok(Off)

U32Deserializer 也自己实现了 deserialize_enum,读变体标识时调 visit_u32,Visitor 默认转到 visit_u64;derive 生成的变体标识 Visitor 里有 0u64 => __Field::__field0、1u64 => __Field::__field1 这样的分支,所以 1u32 读成第二个变体 Off。其他整数的 Deserializer 没有这个 deserialize_enum,只有 u32:数据模型里变体下标的类型就是 u32(serialize_unit_variant 的 variant_index: u32)。

用法二:MapDeserializer 把键值对当成 map

MapDeserializer::new(iter) 收一个产出 (K, V) 的迭代器,K、V 都实现 IntoDeserializer。它的 MapAccess 分两步:next_key_seed 取出下一对,把值暂存到 self.value,用键的 into_deserializer() 喂 seed;next_value_seed 取出暂存的值:

    fn next_value_seed<T>(&mut self, seed: T) -> Result<T::Value, Self::Error>
    where
        T: de::DeserializeSeed<'de>,
    {
        let value = self.value.take();
        // Panic because this indicates a bug in the program rather than an
        // expected failure.
        let value = value.expect("MapAccess::next_value called before next_key");
        seed.deserialize(value.into_deserializer())
    }

先调 next_value 再调 next_key 是 Visitor 写错了,不是输入的问题,所以这里 panic 而不是返回错误。两步协议本身见 SeqAccess、MapAccess 与 EnumAccess。

拿它从一组字符串键值对读一个结构体:

#[derive(Deserialize, Debug)]
struct Config {
  host: String,
  port: u16,
}

#[derive(Deserialize, Debug)]
struct Names {
  host: String,
  user: String,
}

// main 里:
  let mut env: HashMap<String, String> = HashMap::new();
  env.insert("host".into(), "localhost".into());
  env.insert("user".into(), "alice".into());
  let names = Names::deserialize(MapDeserializer::<_, value::Error>::new(env.clone().into_iter()));
  println!("Names  <- HashMap<String,String> : {names:?}");
  env.remove("user");
  env.insert("port".into(), "8080".into());
  let cfg = Config::deserialize(MapDeserializer::<_, value::Error>::new(env.into_iter()));
  println!("Config <- HashMap<String,String> : {cfg:?}");

  // same, but the values are serde_json::Value
  let mut m: BTreeMap<String, serde_json::Value> = BTreeMap::new();
  m.insert("host".into(), "localhost".into());
  m.insert("port".into(), 8080.into());
  let cfg = Config::deserialize(MapDeserializer::<_, serde_json::Error>::new(m.into_iter()));
  println!("Config <- BTreeMap<String,Value> : {cfg:?}");
Names  <- HashMap<String,String> : Ok(Names { host: "localhost", user: "alice" })
Config <- HashMap<String,String> : Err(Error("invalid type: string \"8080\", expected u16"))
Config <- BTreeMap<String,Value> : Ok(Config { host: "localhost", port: 8080 })

全是 String 字段时没问题;port: u16 拿到的是 StringDeserializer,它不看 deserialize_u16 这个提示,照样调 visit_string,u16 的 Visitor 不收字符串。把值换成 serde_json::Value 就行了:Value 实现了 IntoDeserializer<'de, serde_json::Error>,它自己就是一个完整的 Deserializer,数字交出去的是 visit_u64。

📝NOTE
没有蠢问题

问: axum 的 Query<T> 用 serde_urlencoded 读 ?port=8080,port: u16 是可以的。query 里的值也全是字符串,它是怎么做到的?

答: serde_urlencoded 0.7.1 的 Deserializer 内部就是一个 MapDeserializer<'de, PartIterator<'de>, Error>,区别在值的类型:它没有用 StringDeserializer,而是自己写了一个 Part,实现 IntoDeserializer(原样返回自己)和 Deserializer,并且听提示。deserialize_u16 这一类方法由一个宏生成,先按提示解析字符串,再借 serde 的基本类型 Deserializer 交出去(serde_urlencoded-0.7.1/src/de.rs):

macro_rules! forward_parsed_value {
    ($($ty:ident => $method:ident,)*) => {
        $(
            fn $method<V>(self, visitor: V) -> Result<V::Value, Self::Error>
                where V: de::Visitor<'de>
            {
                match self.0.parse::<$ty>() {
                    Ok(val) => val.into_deserializer().$method(visitor),
                    Err(e) => Err(de::Error::custom(e))
                }
            }
        )*
    }
}

"8080".parse::<u16>() 得到 8080u16,into_deserializer() 得到 U16Deserializer,它调 visit_u16。同样的 Config 用 serde_urlencoded::from_str("host=localhost&port=8080") 读出来是 Ok(Config { host: "localhost", port: 8080 })。axum 那边怎么调它,见 Path、Query 与请求元数据和 body 提取器。

MapDeserializer 和 SeqDeserializer 都有一个 end():deserialize_any 在 visit_map / visit_seq 返回之后调它,检查迭代器里还有没有没被读走的元素。SeqDeserializer::end:

    pub fn end(self) -> Result<(), E> {
        let remaining = self.iter.count();
        if remaining == 0 {
            Ok(())
        } else {
            // First argument is the number of elements in the data, second
            // argument is the number of elements expected by the Deserialize.
            Err(de::Error::invalid_length(
                self.count + remaining,
                &ExpectedInSeq(self.count),
            ))
        }
    }

count 是已经交出去的元素个数。格式自己在 visit_seq 之后查「后面还有没有东西」,和 serde_json 的 end() 查尾部字符是同一种职责:Visitor 只读它要的那么多,多出来的由格式发现。

❗IMPORTANT
动脑筋:<(u8, u8)>::deserialize(vec![1u8, 2, 3].into_deserializer()) 报什么?反过来,(u8, u8, u8) 读 vec![1u8, 2] 呢?

平台 M2 Max、rustc 1.99.0,错误类型 value::Error:

  let t = <(u8, u8)>::deserialize(IntoDeserializer::<value::Error>::into_deserializer(vec![1u8, 2, 3]));
  println!("(u8,u8) <- vec![1,2,3] : {t:?}");
  let t = <(u8, u8, u8)>::deserialize(IntoDeserializer::<value::Error>::into_deserializer(vec![1u8, 2]));
  println!("(u8,u8,u8) <- vec![1,2] : {t:?}");
两个错误的原文分别由谁生成?

借用:BorrowedStrDeserializer

StrDeserializer 调的是 visit_str,交出去的字符串只在这次调用里有效,&str 目标借不到它;BorrowedStrDeserializer 调 visit_borrowed_str,字符串的生命周期就是 'de:

&str <- StrDeserializer         : Err(Error("invalid type: string \"borrowed?\", expected a borrowed string"))
&str <- BorrowedStrDeserializer : Ok("borrowed?")

两者的区别正是生命周期 'de 与借用里 visit_str 和 visit_borrowed_str 的区别。BorrowedStrDeserializer 没有对应的 IntoDeserializer for &str(那个位置已经给了 StrDeserializer),只能 new。配合 MapDeserializer,可以零拷贝地把一段自定义格式的文本读成 BTreeMap<&str, &str>(src/bin/borrowmap.rs):

use std::collections::BTreeMap;

use serde::Deserialize;
use serde::de::value::{BorrowedStrDeserializer, Error, MapDeserializer};

fn main() {
  let input = String::from("host=localhost;user=alice");
  let pairs = input.split(';').filter_map(|kv| kv.split_once('=')).map(|(k, v)| {
    (BorrowedStrDeserializer::<Error>::new(k), BorrowedStrDeserializer::<Error>::new(v))
  });
  let map = BTreeMap::<&str, &str>::deserialize(MapDeserializer::new(pairs)).unwrap();
  let host = map["host"];
  println!("{map:?}");
  println!("host points into input: {}", input.as_ptr() as usize == host.as_ptr() as usize - "host=".len());
}
{"host": "localhost", "user": "alice"}
host points into input: true

迭代器的元素是 (BorrowedStrDeserializer, BorrowedStrDeserializer),MapDeserializer 对它们调 into_deserializer(),靠的就是上面冷知识里那个「原样返回自己」的 impl。

📝NOTE
冷知识:&str 的 IntoDeserializer 在 1.0.0 前借用过三天

2017-04-14 的提交「Allow borrowing from str's IntoDeserializer」(30e8c84d)把 &'a str 的 impl 改成 IntoDeserializer<'a, E>,StrDeserializer 调 visit_borrowed_str;2017-04-17 的 b8a6e683 把它原样回退了,提交信息只有「This reverts commit 30e8c84d...」,没有写原因,两者都在 1.0.0 之前。回退之后,StrDeserializer<'a, E> 实现的是任意 'de 的 Deserializer<'de>,所以只能调 visit_str。

借用的需求一个月后用新类型补上:PR #940(「add borrowed value deserializers」,首次发布于 1.0.7)加了 BorrowedStrDeserializer 和 BorrowedBytesDeserializer,说明里写「the lifetime of the string is tied to the lifetime of the deserializer. This can be useful when, for example, deserializing into a HashMap<&str, &str> when the keys/values are tied to the deserializer itself.」上面的实验就是这个用例。

用法三:在 visit_map 半路换手

MapAccessDeserializer 解决另一类问题:自己写的 Visitor 已经被格式调进了 visit_map,手里是一个 MapAccess,这时想把剩下的工作交给某个 derive 出来的类型。derive 出来的 Deserialize 要的是一个 Deserializer,不是 MapAccess;MapAccessDeserializer 就是这层包装,deserialize_any 直接 visitor.visit_map(self.map)。

一个字段既可以写成字符串简写,也可以写成完整的结构体:

#[derive(Deserialize, Debug)]
struct Image {
  name: String,
  #[serde(default = "latest")]
  tag: String,
}

fn latest() -> String {
  "latest".into()
}

// Accepts "nginx" or {"name": "nginx", "tag": "1.27"}.
fn string_or_struct<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Image, D::Error> {
  struct V;

  impl<'de> Visitor<'de> for V {
    type Value = Image;

    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
      f.write_str("an image name or an image struct")
    }

    fn visit_str<E: de::Error>(self, s: &str) -> Result<Image, E> {
      Ok(Image { name: s.to_owned(), tag: latest() })
    }

    fn visit_map<A: MapAccess<'de>>(self, map: A) -> Result<Image, A::Error> {
      // Hand the half-consumed map to the derived impl.
      Image::deserialize(MapAccessDeserializer::new(map))
    }
  }

  deserializer.deserialize_any(V)
}

#[derive(Deserialize, Debug)]
struct Service {
  #[serde(deserialize_with = "string_or_struct")]
  image: Image,
}
{"image":"nginx"}                          Ok(Service { image: Image { name: "nginx", tag: "latest" } })
{"image":{"name":"nginx","tag":"1.27"}}    Ok(Service { image: Image { name: "nginx", tag: "1.27" } })
{"image":{"tag":"1.27"}}                   Err("missing field `name` at line 1 column 23")
{"image":7}                                Err("invalid type: integer `7`, expected an image name or an image struct at line 1 column 10")

MapAccessDeserializer 的 Error 是 A::Error,即 serde_json 的错误,所以第三行的 missing field 还带着行列号。数据从头到尾没有缓冲:Image 的 visit_map 直接从 serde_json 的 MapAccess 里读。这和缓冲主线不同,缓冲主线是还不知道该交给谁,只能先读成 Content;这里在 visit_str 和 visit_map 之间做选择的是格式(deserialize_any),进了 visit_map 就已经知道该交给 Image。

serde 自己和 serde_json 在哪里用它

位置用了什么做什么
serde/src/private/de.rs 的 TaggedContentVisitor::visit_seqSeqAccessDeserializer内部标签 enum 的输入是数组时,读完第一个元素(标签),把剩下的 SeqAccess 包起来读成 Content,见内部标签与相邻标签
同文件的 IdentifierDeserializeru64 的 IntoDeserializer、BytesDeserializer、BorrowedBytesDeserializer#[serde(field_identifier)] enum 最后一个变体是 newtype 时,没匹配上的标识交给它(identifier.rs 生成 IdentifierDeserializer::from(__value))。&str 这一支用的是同文件里一对私有的 StrDeserializer / BorrowedStrDeserializer,它们连 deserialize_enum 也转给 deserialize_any
同文件 ContentDeserializer 的 IntoDeserializer实现 IntoDeserializer缓冲主线重放时,Content 树本身就是一个 Deserializer,见 Content
serde_json Value 的 visit_i128I128Deserializer / U128Deserializer把 128 位整数转交给 Number
serde_json EnumDeserializer::variant_seedString 的 into_deserializer()Value 当 enum 读时,变体名用 StringDeserializer 交给变体标识的 Visitor
serde_json BorrowedRawDeserializerBorrowedStrDeserializer借用的 RawValue 经魔法键的值交出原始 JSON 文本
serde_json NumberDeserializer(arbitrary_precision)String 的 into_deserializer()任意精度数字以字符串交出

derive 生成的普通代码不直接用这个模块:字段、变体标识走格式的 deserialize_identifier,未知字段用 IgnoredAny(见标准库类型的实现)。Content 那一套 ContentRefDeserializer、FlatMapDeserializer 是 serde/src/private/de.rs 里私有的,和这里的公开类型是两回事。serde_json 的魔法键和 RawValue 本身在 Value。

value::Error:只有一句话的错误

#[derive(Clone, PartialEq)]
pub struct Error {
    err: ErrorImpl,
}

#[cfg(any(feature = "std", feature = "alloc"))]
type ErrorImpl = Box<str>;
#[cfg(not(any(feature = "std", feature = "alloc")))]
type ErrorImpl = ();

de::Error::custom 把消息 to_string().into_boxed_str() 存起来;invalid_type、unknown_variant 等构造函数都是 de::Error 的默认实现,最后也落到 custom,所以它里面只有拼好的那一句话。没有分配器时连这句话也不存,Display 一律打印 Serde deserialization error。它同时实现了 ser::Error,Debug 打印成 Error("...")(上面实验里看到的就是这个),Display 只打印消息本身。没有位置信息,也没有结构化的错误种类:它是给「没有真正的格式」时用的,比如 FromStr 的 Err。Unexpected 和 Expected 怎么拼出这句话,见错误。

动脑筋答案

(u8,u8) <- vec![1,2,3] : Err(Error("invalid length 3, expected 2 elements in sequence"))
(u8,u8,u8) <- vec![1,2] : Err(Error("invalid length 2, expected a tuple of size 3"))
  • 第一个错误来自 SeqDeserializer::end。二元组的 Visitor 读完两个元素就返回 Ok,它并不知道后面还有。deserialize_any 接着调 end(),迭代器里剩 1 个:invalid_length(self.count + remaining, &ExpectedInSeq(self.count)),即「数据里 3 个、Deserialize 要 2 个」,ExpectedInSeq(2) 打印成 2 elements in sequence。
  • 第二个错误来自元组的 Visitor。读第三个元素时 next_element 返回 None,Visitor 报 invalid_length(2, &self),expected 用的是它自己的 expecting:a tuple of size 3。end() 根本没有机会运行。

同样是「长度不对」,太长由格式(Deserializer)发现,太短由类型(Visitor)发现,所以两句话的后半截不一样。

小结

  • de::value 为基本类型、字符串、字节、()、序列和映射各写了一个最小的 Deserializer:所有 deserialize_* 都转给 deserialize_any,调一次和值类型对应的 visit_*,不看类型提示。转换由目标类型的 Visitor 负责。
  • 字符串类和 U32Deserializer 自己实现 deserialize_enum,按名字或下标选 unit 变体(UnitOnly),所以 "On".into_deserializer() 能读成 enum,1u32 读成第二个变体。
  • IntoDeserializer<'de, E = value::Error> 把值包成这些 Deserializer;E 是参数,方便在别的格式里用(serde_json 的 Value 用 I128Deserializer)。每个 Deserializer 也原样实现了它(1.0.214,PR #2568),可以直接当 MapDeserializer 的元素。
  • MapDeserializer 读 HashMap<String, String> 时,非字符串字段报 invalid type: string "8080", expected u16;值换成 serde_json::Value,或像 serde_urlencoded 的 Part 那样按提示解析,才能读数字。
  • SeqDeserializer / MapDeserializer 的 end() 检查剩余元素:太长由它报 expected 2 elements in sequence,太短由 Visitor 报。
  • BorrowedStrDeserializer(1.0.7,PR #940)调 visit_borrowed_str,能零拷贝地读出 &'de str;StrDeserializer 只能调 visit_str。
  • MapAccessDeserializer 把半路的 MapAccess 包成 Deserializer,用来在 visit_map 里把工作交给 derive 出来的类型,不缓冲,错误保留外层格式的位置。
  • value::Error 只存一句拼好的消息(Box<str>),给没有真正格式的场合用。

💬 关联讨论

发起讨论
加载中...