我怎样才能拥有一个接受可变引用的属性,该引用将比其自身寿命更长?

Won*_*Hau 5 rust

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

struct Environment<'a> {
  parent: Option<&'a mut Environment<'a>>
}
Run Code Online (Sandbox Code Playgroud)

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

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

struct Environment {
  parent: <'a> Option<&'a mut Environment>
}
Run Code Online (Sandbox Code Playgroud)

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

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

let x = 1  // parent environment
let f(y) = {
  // current environment
  x + y
}
Run Code Online (Sandbox Code Playgroud)

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

希望这足够清楚地说明为什么我需要parent引用来超过持有它的结构体。

回顾一下这个问题:如何在一个结构中声明一个属性,该属性可以保存一个可以比自身存活时间更长的自身类型的可变引用?如果这是不可能的,我可以查看哪些替代方案?

Kev*_*eid 2

声明

\n
struct Environment<\'a> {\n  parent: Option<&\'a mut Environment<\'a>>\n}\n
Run Code Online (Sandbox Code Playgroud)\n

并不意味着不能parent比结构更长寿。事实上,结构上的生命周期参数最终总是会引用比结构本身的存在时间更长的生命周期。

\n

您实际遇到的问题是相似的,但更微妙:您写了&\'a mut Environment<\'a>, 并且在这种类型中

\n
    \n
  • Environment<\'a>意味着Environment\ 的内部可变引用对于 有效\'a。这意味着Environment 必须是在生命周期内创建的\'a
  • \n
  • &\'a mut意味着该引用准确有效,\'a这意味着它的引用对象必须在 之前创建过\'a
  • \n
\n

这两个要求几乎互相矛盾,因此实际上不可能满足(除非\'a: \'static)。对此的一般解决方法是通过使用单独的生命周期参数来避免过度限制生命周期:

\n
struct Environment<\'r, \'e> {\n  parent: Option<&\'r mut Environment<\'e, ???>>\n}\n
Run Code Online (Sandbox Code Playgroud)\n

但是,这在您的情况下不起作用,因为存在一个更深层次的问题,暗示了<\'e, ???>:通过拥有引用链&mut,我们允许这些引用的持有者修改任何级别的任何部分该链,特别是用不同的引用替换这些引用。但为了做到这一点,他们需要用具有相同生命周期的东西来替换它\xe2\x80\x94\xc2\xa0 并且这个结构的形状是一个嵌套的链,生命周期越长越好,例如,取两个Environment通过结构的形状可以使两个 s 发生突变以交换其位置,但会违反生命周期。

\n

一种可能的方法在结构内部使用不可变引用,但不如您想要的那么灵活。为了演示这一点,这里有一个简单的范围检查器:

\n
use std::collections::HashSet;\nenum Tree {\n    Definition(String),\n    Use(String),\n    Block(Vec<Tree>),\n}\n\nstruct Environment<\'r> {\n    parent: Option<&\'r Environment<\'r>>,\n    symbols: HashSet<String>,\n}\n\nimpl<\'r> Environment<\'r> {\n    fn contains(&self, x: &str) -> bool {\n        self.symbols.contains(x) || self.parent.filter(|p| p.contains(x)).is_some()\n    }\n}\n\nfn empty() -> Environment<\'static> {\n    Environment {\n        parent: None,\n        symbols: HashSet::new(),\n    }\n}\n\nfn check<\'r>(env: &\'r mut Environment<\'_>, tree: &Tree) -> Result<(), String> {\n    use Tree::*;\n    match tree {\n        Definition(symbol) => {\n            env.symbols.insert(symbol.clone());\n            Ok(())\n        }\n        Use(symbol) => {\n            if !env.contains(symbol) {\n                return Err(symbol.clone());\n            }\n            Ok(())\n        }\n        Block(statements) => {\n            let mut block_env = Environment {\n                parent: Some(env),\n                symbols: HashSet::new(),\n            };\n            for statement in statements.iter() {\n                check(&mut block_env, statement)?;\n            }\n            Ok(())\n        }\n    }\n}\n\n#[test]\nfn tests() {\n    use Tree::*;\n    assert_eq!(\n        check(\n            &mut empty(),\n            &Block(vec![Definition("x".to_owned()), Use("x".to_owned())])\n        ),\n        Ok(())\n    );\n    assert_eq!(\n        check(\n            &mut empty(),\n            &Block(vec![Definition("x".to_owned()), Use("y".to_owned())])\n        ),\n        Err("y".to_owned())\n    );\n    assert_eq!(\n        check(\n            &mut empty(),\n            &Block(vec![\n                Definition("x".to_owned()),\n                Block(vec![Use("x".to_owned())])\n            ])\n        ),\n        Ok(())\n    );\n}\n
Run Code Online (Sandbox Code Playgroud)\n

请注意声明

\n
struct Environment<\'r> {\n    parent: Option<&\'r Environment<\'r>>,\n    symbols: HashSet<String>,\n}\n
Run Code Online (Sandbox Code Playgroud)\n

contains Option<&\'r Environment<\'r>>,这正是我之前告诉你不要做的。Environment<\'r>不会过度限制生命周期的原因是不可变引用的指示对象的类型是协变的\xe2\x80\x94 这意味着如果我们想要 anEnvironment<\'r>我们可以接受 anEnvironment<\'e>只要\'eoutlives \'r。另一方面,可变引用需要不变性:它们必须完全匹配。(这是因为对于不可变引用,数据只能从其中流出但是对于可变引用,数据可以流入或流出,因此如果任一方向上存在生命周期不匹配,则这是不合理的。)

\n

我上面写的警告是,不可能改变链上更远的环境,如果您正在执行类型推断(以便使用可以确定声明的类型),您可能会想要这种情况。我并不完全确定,但我认为为了做到这一点,您需要诉诸内部可变性,以便即使没有可变引用也可以改变某些内容。

\n

这是修改为使用内部可变性工具的上述示例std::cell::RefCell。请注意,此代码实际上并未使用额外的灵活性 \xe2\x80\x94,但它就在那里;您可以symbols使用显式运行时检查操作来修改任何父级的.symbols.borrow_mut()

\n
use std::cell::RefCell;\nuse std::collections::HashSet;\n\nenum Tree {\n    Definition(String),\n    Use(String),\n    Block(Vec<Tree>),\n}\n\nstruct Environment<\'r> {\n    parent: Option<&\'r Environment<\'r>>,\n    symbols: RefCell<HashSet<String>>,\n}\n\nimpl<\'r> Environment<\'r> {\n    fn contains(&self, x: &str) -> bool {\n        self.symbols.borrow().contains(x) || self.parent.filter(|p| p.contains(x)).is_some()\n    }\n}\n\nfn empty() -> Environment<\'static> {\n    Environment {\n        parent: None,\n        symbols: RefCell::new(HashSet::new()),\n    }\n}\n\nfn check<\'r>(env: &\'r Environment<\'_>, tree: &Tree) -> Result<(), String> {\n    use Tree::*;\n    match tree {\n        Definition(symbol) => {\n            env.symbols.borrow_mut().insert(symbol.clone());\n            Ok(())\n        }\n        Use(symbol) => {\n            if !env.contains(symbol) {\n                return Err(symbol.clone());\n            }\n            Ok(())\n        }\n        Block(statements) => {\n            let block_env = Environment {\n                parent: Some(env),\n                symbols: RefCell::new(HashSet::new()),\n            };\n            for statement in statements.iter() {\n                check(&block_env, statement)?;\n            }\n            Ok(())\n        }\n    }\n}\n\n#[test]\nfn tests() {\n    use Tree::*;\n    assert_eq!(\n        check(\n            &mut empty(),\n            &Block(vec![Definition("x".to_owned()), Use("x".to_owned())])\n        ),\n        Ok(())\n    );\n    assert_eq!(\n        check(\n            &mut empty(),\n            &Block(vec![Definition("x".to_owned()), Use("y".to_owned())])\n        ),\n        Err("y".to_owned())\n    );\n    assert_eq!(\n        check(\n            &mut empty(),\n            &Block(vec![\n                Definition("x".to_owned()),\n                Block(vec![Use("x".to_owned())])\n            ])\n        ),\n        Ok(())\n    );\n}\n
Run Code Online (Sandbox Code Playgroud)\n