You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

理解依赖生命周期:如何告知编译器一个生命周期需长于另一个?

问题:Serde反序列化带生命周期的结构体报错

代码示例

用户编写的Rust代码如下:

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
pub struct Tokens<'a> {
    buffer: String,
    list: Vec<Token<'a>>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Token<'a> {
    term: &'a str,
}

编译错误

编译时出现如下错误:

error: lifetime may not live long enough
 --> src/pipeline/tokenizers/test.rs:6:5
  |
3 | #[derive(Serialize, Deserialize, Debug)]
  |                     ----------- lifetime `'de` defined here
4 | pub struct Tokens<'a> {
  |                   -- lifetime `'a` defined here
5 |     buffer: String,
6 |     list: Vec<Token<'a>>,
  |     ^^^^ requires that `'de` must outlive `'a`
  |
  = help: consider adding the following bound: `'de: 'a`

用户已知Token中的term始终是Tokens中buffer的切片,但不清楚如何指定这种关系,也不知道如何添加编译器提示的生命周期约束,询问该实现是否可行,以及正确的语法。


解答

原实现不可行的原因

这种直接用派生宏的实现不可行,核心原因在于Serde的反序列化机制:

  • 反序列化结构体时会逐个处理字段,当反序列化list字段时,buffer的内存还未完全确定(String在反序列化过程中可能发生内存分配/移动),无法生成指向它的有效引用。
  • 编译器提示的'de: 'a约束本质上无法满足:'de是反序列化过程的临时生命周期,反序列化完成后就会结束,而'a是结构体Tokens的生命周期,必须比'de更长,这会导致悬垂引用,违反Rust的内存安全规则。

正确实现方式

方法1:先反序列化所有权版本,再生成引用版本

这种方法最简单,先把Token定义为拥有所有权的String,反序列化完成后再通过方法生成带引用的结构体:

use serde::{Deserialize, Serialize};

// 用于反序列化的所有权版本
#[derive(Serialize, Deserialize, Debug)]
pub struct TokensOwned {
    buffer: String,
    list: Vec<TokenOwned>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct TokenOwned {
    term: String,
}

// 带引用的只读版本,只能从TokensOwned生成
#[derive(Debug)]
pub struct Tokens<'a> {
    buffer: &'a str,
    list: Vec<Token<'a>>,
}

#[derive(Debug)]
pub struct Token<'a> {
    term: &'a str,
}

impl TokensOwned {
    // 生成带引用的Tokens实例
    pub fn as_tokens(&self) -> Tokens<'_> {
        Tokens {
            buffer: &self.buffer,
            list: self.list
                .iter()
                .map(|tok| Token { term: &tok.term })
                .collect(),
        }
    }
}

方法2:手动实现Deserialize trait

如果需要直接得到带引用的Tokens,可以手动实现反序列化逻辑,先反序列化出中间结构,再从buffer生成切片:

use serde::{de::Deserializer, Deserialize, Serialize};

#[derive(Serialize, Debug)]
pub struct Tokens<'a> {
    buffer: String,
    list: Vec<Token<'a>>,
}

#[derive(Serialize, Debug)]
pub struct Token<'a> {
    term: &'a str,
}

// 中间结构体,用于接收反序列化的原始数据
#[derive(Deserialize)]
struct TokensDeser {
    buffer: String,
    list: Vec<String>,
}

impl<'de> Deserialize<'de> for Tokens<'de> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let raw = TokensDeser::deserialize(deserializer)?;
        let buffer = raw.buffer;

        // 验证每个term确实是buffer的子串,生成对应的切片
        let list = raw
            .list
            .into_iter()
            .map(|term| {
                let start = buffer
                    .find(&term)
                    .ok_or_else(|| serde::de::Error::custom("term不是buffer的子串"))?;
                let end = start + term.len();
                Ok(Token { term: &buffer[start..end] })
            })
            .collect::<Result<_, D::Error>>()?;

        Ok(Tokens { buffer, list })
    }
}

说明

  • 方法1更简洁安全,适合大多数场景,避免手动处理反序列化的复杂逻辑。
  • 方法2需要额外的子串验证步骤,确保term确实来自buffer,否则会引发内存安全问题。

内容的提问来源于stack exchange,提问作者ccleve

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 20:50:53