首页 > 解决方案 > 如何正确解决结构中的生命周期推断?

问题描述

Rust游乐场代码在这里

我有一个具有生命周期的 Token 结构'tok,而扫描器具有生命周期'lexer。我在另一个结构解析器中使用它们,然后我遇到了一个问题:

pub struct Token<'tok> {
    pub value: Cow<'tok, str>,
    pub line: usize,
}

pub struct Scanner {
    pub source: Vec<char>,
    pub current: usize,
    pub line: usize,
}

pub struct Parser<'lexer> {
    pub curr: &'lexer Token<'lexer>,
    pub prev: &'lexer Token<'lexer>,
    scanner: &'lexer mut Scanner,
}

impl <'lexer> Parser<'lexer> {
    pub fn advance(&mut self) {
        self.prev = self.curr;
        self.curr = &self.scanner.next(); // cannot inference lifetime
    }
}

我认为问题是 Token 有生命周期'tok,而借用检查器不知道两者之间的关系'tok'lexer因此它无法推断出正确的生命周期。

但是,我可以通过将其修改为更新的代码来避免该问题:

pub struct Parser<'lexer> {
    pub curr: Token<'lexer>,
    pub prev: Token<'lexer>,
    scanner: &'lexer mut Scanner,
}

impl <'lexer> Parser<'lexer> {
    pub fn advance(&mut self) {
        let prev = std::mem::replace(&mut self.curr, self.scanner.next());
        self.prev = prev;
    }
}

并且使用 Token 生成的next()是静态的:

impl Scanner {
    pub fn next(&mut self) -> Token<'static> {
        Token {
            value: Cow::from(""),
            line: 0,
        }
    }
}

它确实可以编译,但我认为这并不理想,因为所有标记都从扫描仪克隆到解析器(它们不再是引用)并一直存在到解析器生命的尽头。所以内存使用量翻了一番。有没有更合适的方法来处理这个问题?

标签: rust

解决方案


实际上,您的代码结构对于处理借用检查器并不理想,原因如下:

  • Tokenstruct 应该被拥有,struct 本身不需要任何分配(因为令牌是拥有的,所以需要一些 indrection 来允许 prev <-> next 切换)
  • 任何人都不Parser应该Lexer拥有基础数据,因此很容易绑定适当的生命周期
  • 在我们的例子Vec<Char>中不是一个友好的类型(我们不需要拥有数据,这将更难让编译器理解生命周期),相反我们将使用 &'str 但你可以重现确切的带有 &[char] 的行为)

这是一个编译得很好的例子

pub struct Token<'source> {
    pub value: Cow<'source, str>,
    pub line: usize,
}

pub struct Scanner<'source> {
    pub source: &'source str,
    pub current: usize,
    pub line: usize,
}

pub struct Parser<'source> {
    pub curr: Option<Token<'source>>,
    pub prev: Option<Token<'source>>,
    scanner: Scanner<'source>,
}
impl <'source>Scanner<'source> {
    pub fn next(&'source /* DONT Forget to bound 'source to `self` */ self) -> Token<'source> {
        Token {
            value: Cow::from(self.source), /* `self.source` is bound to `'source` so the compiler understand that the token lifetime is the same than the source's one */
            line: 0,
        }
    }
}

impl <'lexer> Parser<'lexer> {
    pub fn advance(&'lexer mut self) {
        self.prev = self.curr.take();
        self.curr = Some(self.scanner.next());
    }
}

推荐阅读