Warm tip: This article is reproduced from serverfault.com, please click

rust-我如何拥有一个带有可变引用的属性,该属性将自身失效?

(rust - How can I have a property that takes a mutable reference that will outlive itself?)

发布于 2020-11-29 06:32:56

基本上我有这个结构(省略了不必要的细节):

struct Environment<'a> {
  parent: Option<&'a mut Environment<'a>>
}

这个结构实际上不是我想要的,因为我希望parent引用比包含引用的结构更长寿。

我可以想象这可以通过使用更高级别的生命周期量化来实现,例如:

struct Environment {
  parent: <'a> Option<&'a mut Environment>
}

但是很明显,上面的代码不起作用(至少在rustc 1.44之前)。

我之所以需要它,是因为我正在尝试实现可以​​处理以下(示例)代码的类型检查器:

let x = 1  // parent environment
let f(y) = {
  // current environment
  x + y
}

因此,当我在内进行符号查找时f,例如x,我将首先在当前环境下进行查找,如果未找到,则将在父环境中进行递归查找。在内部进行类型检查后ff将丢弃的环境,但父环境仍应保留,以便可以对下一条语句进行类型检查。

希望这一点足够清楚,可以说明为什么我需要parent引用才能使保存引用的结构失效。

概括一下这个问题:如何在结构中声明一个属性,该结构可以保存自己的类型的可变引用,并且该类型的引用可以生存下去?如果这不可能,那么我可以考虑的替代方案是什么?

Questioner
Wong Jia Hau
Viewed
11
Kevin Reid 2020-11-30 01:35:18

报关单

struct Environment<'a> {
  parent: Option<&'a mut Environment<'a>>
}

意味着parent不能活得比的结构。实际上,结构上的生存期参数将始终以比该结构本身存在更长的生存期为最终对象

你实际上遇到的问题是类似的,但是很微妙:你编写了&'a mut Environment<'a>,并且是这种类型的

  • Environment<'a>表示Environment的内部可变引用对有效'a这意味着Environment 必须已在生命周期内创建了该对象'a
  • &'a mut表示该引用确切地是有效的,'a这意味着它的引用必须在之前创建的'a

这两个要求几乎彼此矛盾,因此实际上是无法满足的(除非'a: 'static)。通用解决方案是通过使用单独的生命周期参数来避免过度约束生命周期:

struct Environment<'r, 'e> {
  parent: Option<&'r mut Environment<'e, ???>>
}

但是,这不适用于你的情况,因为存在一个更深层次的问题,提示如下<'e, ???>:通过具有&mut引用,我们允许这些引用的持有者修改该链任何级别的任何部分,并且特别是用不同的参考文献代替那些参考文献。但是,为了做到这一点,他们需要的东西来取代它具有相同的寿命-这种结构的形状是嵌套在较长的你去-更远寿命的链条,因此,例如,以两个Environment小号并通过结构的形状来改变它们以交换位置是可能的,但是会违反生命周期。

东西可能的,但不够灵活,你可能想,就是用一成不变的结构内引用。为了说明这一点,这是一个简单的作用域检查器:

use std::collections::HashSet;
enum Tree {
    Definition(String),
    Use(String),
    Block(Vec<Tree>),
}

struct Environment<'r> {
    parent: Option<&'r Environment<'r>>,
    symbols: HashSet<String>,
}

impl<'r> Environment<'r> {
    fn contains(&self, x: &str) -> bool {
        self.symbols.contains(x) || self.parent.filter(|p| p.contains(x)).is_some()
    }
}

fn empty() -> Environment<'static> {
    Environment {
        parent: None,
        symbols: HashSet::new(),
    }
}

fn check<'r>(env: &'r mut Environment<'_>, tree: &Tree) -> Result<(), String> {
    use Tree::*;
    match tree {
        Definition(symbol) => {
            env.symbols.insert(symbol.clone());
            Ok(())
        }
        Use(symbol) => {
            if !env.contains(symbol) {
                return Err(symbol.clone());
            }
            Ok(())
        }
        Block(statements) => {
            let mut block_env = Environment {
                parent: Some(env),
                symbols: HashSet::new(),
            };
            for statement in statements.iter() {
                check(&mut block_env, statement)?;
            }
            Ok(())
        }
    }
}

#[test]
fn tests() {
    use Tree::*;
    assert_eq!(
        check(
            &mut empty(),
            &Block(vec![Definition("x".to_owned()), Use("x".to_owned())])
        ),
        Ok(())
    );
    assert_eq!(
        check(
            &mut empty(),
            &Block(vec![Definition("x".to_owned()), Use("y".to_owned())])
        ),
        Err("y".to_owned())
    );
    assert_eq!(
        check(
            &mut empty(),
            &Block(vec![
                Definition("x".to_owned()),
                Block(vec![Use("x".to_owned())])
            ])
        ),
        Ok(())
    );
}

注意声明

struct Environment<'r> {
    parent: Option<&'r Environment<'r>>,
    symbols: HashSet<String>,
}

包含Option<&'r Environment<'r>>,这正是我之前告诉你不要执行的操作。其原因Environment<'r>并不过度约束寿命是不可变引用的指示对象的类型是协变的-这意味着,如果我们想要一个Environment<'r>我们可以接受的Environment<'e>,只要'e会超越'r另一方面,可变引用需要不变性:它们必须完全匹配。(这是因为对于不变的引用,数据只能从其中流出,但是在可变的引用下,数据可以流入或流出,因此,如果在任一方向上存在生命周期不匹配,则将是不合理的。)

我上面所写的警告是,不可能在更远的链上改变环境,如果你正在执行类型推断你可以使用它来确定声明的类型),则可能需要这样做我并不完全确定,但是我认为要做到这一点,你需要诉诸内部可变性,即使没有可变的引用也可以使你进行某些变异。

这是上面的示例,已修改为使用内部可变性工具std::cell::RefCell请注意,该代码实际上并没有使用额外的灵活性,但它确实存在。你可以symbols使用显式的运行时检查.symbols.borrow_mut()操作来修改任何父对象的

use std::cell::RefCell;
use std::collections::HashSet;

enum Tree {
    Definition(String),
    Use(String),
    Block(Vec<Tree>),
}

struct Environment<'r> {
    parent: Option<&'r Environment<'r>>,
    symbols: RefCell<HashSet<String>>,
}

impl<'r> Environment<'r> {
    fn contains(&self, x: &str) -> bool {
        self.symbols.borrow().contains(x) || self.parent.filter(|p| p.contains(x)).is_some()
    }
}

fn empty() -> Environment<'static> {
    Environment {
        parent: None,
        symbols: RefCell::new(HashSet::new()),
    }
}

fn check<'r>(env: &'r Environment<'_>, tree: &Tree) -> Result<(), String> {
    use Tree::*;
    match tree {
        Definition(symbol) => {
            env.symbols.borrow_mut().insert(symbol.clone());
            Ok(())
        }
        Use(symbol) => {
            if !env.contains(symbol) {
                return Err(symbol.clone());
            }
            Ok(())
        }
        Block(statements) => {
            let block_env = Environment {
                parent: Some(env),
                symbols: RefCell::new(HashSet::new()),
            };
            for statement in statements.iter() {
                check(&block_env, statement)?;
            }
            Ok(())
        }
    }
}

#[test]
fn tests() {
    use Tree::*;
    assert_eq!(
        check(
            &mut empty(),
            &Block(vec![Definition("x".to_owned()), Use("x".to_owned())])
        ),
        Ok(())
    );
    assert_eq!(
        check(
            &mut empty(),
            &Block(vec![Definition("x".to_owned()), Use("y".to_owned())])
        ),
        Err("y".to_owned())
    );
    assert_eq!(
        check(
            &mut empty(),
            &Block(vec![
                Definition("x".to_owned()),
                Block(vec![Use("x".to_owned())])
            ])
        ),
        Ok(())
    );
}