1816 字
约 6 分钟
0
为 TodoItem 实现特征

为 TodoItem 实现特征

在先前的开发中, 我们为 TodoItem 实现了序列化和反序列化方法。这两种方法在开发过程中相当常见。 我们如果要为每个方法都去实现一遍有点太繁琐了。

因此, 我们可以声明一个特征, 从而将这些通用行为给抽离出来。

首先定义 Serializer 特征表示序列化和反序列化方法集合。然后为 TodoItem 实现 Serializer 特征。

pub trait Serializer {
  fn serialize(&self) -> String;
  fn deserialize<S: Into<String>>(s: S) -> Self;
}

impl Serializer for TodoItem {
  fn deserialize<S: Into<String>>(s: S) -> Self {
    serde_json::from_str(&s.into()).unwrap()
  }

  fn serialize(&self) -> String {
    serde_json::to_string(self).unwrap()
  }
}

默认实现特征

以上代码中, 尽管我们已经将序列化和反序列化方法抽离。但仍然需要手动实现方法体。这依然较为繁琐。

因此我们可以使用默认实现来取消手动实现方法体。

pub trait Serializer {
  fn serialize(&self) -> String {
    serde_json::to_string(self).unwrap()
  }

  fn deserialize<S: Into<String>>(s: S) -> Self {
    serde_json::from_str(&s.into()).unwrap()
  }
}

impl Serializer for TodoItem {}

将代码改成以上内容, 然后运行。会发现有报错。我们先解决 serialize 方法的报错。

error[E0277]: the trait bound `Self: Serialize` is not satisfied
    --> src\todo\core.rs:68:31
     |
68   |         serde_json::to_string(self).unwrap()
     |         --------------------- ^^^^ the trait `Serialize` is not implemented for `Self`
     |         |
     |         required by a bound introduced by this call
     |
     = 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
note: required by a bound in `serde_json::to_string`
    --> C:\Users\Administrator\.cargo\registry\src\rsproxy.cn-e3de039b2554c837\serde_json-1.0.128\src\ser.rs:2209:17
     |
2207 | pub fn to_string<T>(value: &T) -> Result<String>
     |        --------- required by a bound in this function
2208 | where
2209 |     T: ?Sized + Serialize,
     |                 ^^^^^^^^^ required by this bound in `to_string`
help: consider further restricting `Self`
     |
67   |     fn serialize(&self) -> String where Self: Serialize {
     |                                   +++++++++++++++++++++

报错内容是 the trait bound `Self: Serialize` is not satisfied。 这是因为 Self 类型没有满足 serde_json::to_string(self) 要求的类型约束导致的。

我们可以查看 serde_json::to_string 方法的定义。

serde_json::ser
pub fn to_string<T>(value: &T) -> Result<String>
where
  T: ?Sized + Serialize,
// ...

可以看见, 它使用了 where 子句, 要求 T 类型必须实现 ?SizedSerialize 特征。

我们将类型约束补上。

pub trait Serializer
where
  Self: Sized + Serialize,
{
  fn serialize(&self) -> String {
    serde_json::to_string(self).unwrap()
  }

  fn deserialize<S: Into<String>>(s: S) -> Self {
    serde_json::from_str(&s.into()).unwrap()
  }
}

impl Serializer for TodoItem {}

再次运行, serialize 方法不再报错, 现在只剩下 deserialize 方法的报错了。

deserialize 方法的报错如下:

error[E0277]: the trait bound `Self: Deserialize<'_>` is not satisfied
    --> src\todo\core.rs:75:9
     |
75   |         serde_json::from_str(&s.into()).unwrap()
     |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Deserialize<'_>` is not implemented for `Self`
     |
     = note: for local types consider adding `#[derive(serde::Deserialize)]` to your `Self` type
     = note: for types from other crates check whether the crate offers a `serde` feature flag
note: required by a bound in `serde_json::from_str`
    --> C:\Users\Administrator\.cargo\registry\src\rsproxy.cn-e3de039b2554c837\serde_json-1.0.128\src\de.rs:2680:8
     |
2678 | pub fn from_str<'a, T>(s: &'a str) -> Result<T>
     |        -------- required by a bound in this function
2679 | where
2680 |     T: de::Deserialize<'a>,
     |        ^^^^^^^^^^^^^^^^^^^ required by this bound in `from_str`
help: consider further restricting `Self`
     |
74   |     fn deserialize<S: Into<String>>(s: S) -> Self where Self: Deserialize<'_> {
     |                                                   +++++++++++++++++++++++++++

报错信息: the trait bound `Self: Deserialize<'_>` is not satisfied 可以看见, 也是因为没有满足类型约束导致的。

编译器也有提示我们增加约束。

补上约束, 代码如下:

pub trait Serializer
where
  Self: Sized + Serialize + Deserialize<'_>,
{
  fn serialize(&self) -> String {
    serde_json::to_string(self).unwrap()
  }

  fn deserialize<S: Into<String>>(s: S) -> Self {
    serde_json::from_str(&s.into()).unwrap()
  }
}

重新运行, deserialize 的报错消失了。但出现了新的报错:

error[E0637]: `'_` cannot be used here
  --> src\todo\core.rs:68:43
   |
68 |     Self: Sized + Serialize + Deserialize<'_>,
   |                                           ^^ `'_` is a reserved lifetime name

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

报错信息: `'_` cannot be used here, 表示这里不能使用 '_, `'_` is a reserved lifetime name 提示我们 '_ 是保留的生命周期名称。

生命周期

生命周期, 通常指某个事物从开始到结束的一个完整的过程。

在 Rust 中, 它是一个编译期概念, 用于检查引用是否合法, 避免悬垂指针等问题。 通常情况下, 我们不需要手动标注生命周期, 编译器会自动推导。 只有当编译器无法确定时, 才需要手动标注。

可以将 Rust 中的生命周期简单的认为就是引用的有效作用域。

{
  let r;
  {
    let n = 0;
    r = &n;
  }
  println!("r: {}", r);
}

以上代码通过花括号被分为两层, 在第一层, 声明了变量 r 但未赋值。 在第二层, 声明了变量 n, 并将 n 的地址赋值给 r

n 是一个局部变量, 它的生命周期在第二层结束, 即花括号结束。 而 r 是一个引用, 它指向 n, 它的生命周期在 r 所在的作用域内, 即第一层的花括号内。

n 的生命周期比 r 的生命周期短, 这没有问题。但是 r 被赋值为 n 的引用, 就会导致出问题。 因为 n 会在 n 的生命周期结束后被销毁, 而 r 指向了一个已经被销毁的变量。

回到新的 deserialize 方法报错。'_ 是一个特殊的生命周期标记。它被用于生命周期省略或临时生命周期标注。 它可以被编译器自动推导。但在类型约束这里并不被允许。

Rust 要求在类型约束时, 必须手动标注明确的生命周期名称。因为编译器无法在这里推断出具体的生命周期。

我们将 '_ 替换为 'a, 再次运行。

需要注意的是, Rust 的声明周期命名并没有什么特别要求, 只是一般通常使用单个小写字母命名。

原先的报错消失了, 新的报错出现了。

高阶生命周期绑定

error[E0261]: use of undeclared lifetime name `'a`
  --> src\todo\core.rs:68:43
   |
68 |     Self: Sized + Serialize + Deserialize<'a>,
   |                                           ^^ undeclared lifetime
   |
   = note: for more information on higher-ranked polymorphism, visit https://doc.rust-lang.org/nomicon/hrtb.html
help: consider making the bound lifetime-generic with a new `'a` lifetime
   |
68 |     Self: Sized + Serialize + for<'a> Deserialize<'a>,
   |                               +++++++
help: consider making the bound lifetime-generic with a new `'a` lifetime
   |
68 |     for<'a> Self: Sized + Serialize + Deserialize<'a>,
   |     +++++++
help: consider introducing lifetime `'a` here
   |
66 | pub trait Serializer<'a>
   |                     ++++

还是聚焦报错内容 use of undeclared lifetime name `'a` , 意思是我们使用了未声明的生命周期。编译器也给出多种解决方法。

以下是几种解决方法的含义:

  • Self: Sized + Serialize + for<'a> Deserialize<'a>: 这是最常见通用的写法, 使用了高阶生命周期约束, 表示不论 'a 是什么生命周期, Self 都能实现特征 Deserialize<'a>
  • pub trait Serializer<'a>: 这让特征本身携带生命周期参数, 可以在该特征的所有方法中使用, 但会导致使用特征时需要额外传递生命周期, 侵入性较强。
  • for<'a> Self: Sized + Serialize + Deserialize<'a>,: 同样使用了高阶生命周期约束, 但 'a 被作用于整个约束条件, 表示不论 'a 是什么生命周期都能让类型约束成立。

我们使用第一种即可。改造完毕后, 再次运行, 这次没有再报错了。

为 TodoItem 实现特征
http://www.clxhxhhr.top/posts/453/
作者
clxstart
发布于
2026-09-04
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。
文章目录