简体   繁体   English

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

[英]How can I have a property that takes a mutable reference that will outlive itself?

Basically I have this struct (unnecessary details elided):基本上我有这个结构(省略了不必要的细节):

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

This struct actually is not what I intended, because I wanted the parent reference to outlive the struct holding the reference.这个结构实际上不是我想要的,因为我希望parent引用比持有引用的结构长。

I can imagine that this is possible with higher-ranked lifetime quantification, for example:我可以想象这可以通过更高级别的生命周期量化来实现,例如:

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

But obviously the code above don't work (at least prior to rustc 1.44).但显然上面的代码不起作用(至少在 rustc 1.44 之前)。

The reason why I need this is because I'm trying to implement type checker that can process the following (example) code:我需要这个的原因是因为我正在尝试实现可以处理以下(示例)代码的类型检查器:

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

So when I do a symbol lookup inside f , say x , I will first do a lookup at the current environment, if not found a recursive lookup will be done in parent environment.因此,当我在f中进行符号查找时,例如x ,我将首先在当前环境中进行查找,如果未找到,将在父环境中进行递归查找。 After typechecking is done inside f , the environment of f will be thrown away, but the parent environment should still retain so that the next statement can be type checked.f内部完成类型检查后, f的环境将被丢弃,但父环境仍应保留,以便可以对下一条语句进行类型检查。

Hopefully this is clear enough to demonstrate why I needed the parent reference to outlive the struct holding it.希望这足以清楚地说明为什么我需要parent引用来比持有它的结构更长寿。

To recap the question: how to declare a property inside a struct that can hold a mutable reference of its own type that can outlive itself?概括一下这个问题:如何在一个结构中声明一个属性,该属性可以包含其自身类型的可变引用,该引用可以比自身寿命更长? If this is not possible what are the alternatives that I can look at?如果这是不可能的,我可以考虑哪些替代方案?

The declaration宣言

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

does not mean that the parent cannot outlive the struct.并不意味着parent级不能超过结构。 In fact, lifetime parameters on structs will always end up referring to lifetimes that are longer than the struct itself's existence.事实上,结构上的生命周期参数最终总是指比结构本身存在的生命周期更长的生命周期。

The problem you actually have is similar, but subtler: you wrote &'a mut Environment<'a> , and in this type您实际遇到的问题是相似的,但更微妙:您编写了&'a mut Environment<'a> ,并且在这种类型中

  • Environment<'a> means that the Environment 's internal mutable reference is valid for 'a . Environment<'a>表示Environment的内部可变引用对'a有效。 This implies that the Environment must have been created while within the lifetime 'a .这意味着必须在生命周期'a内创建Environment
  • &'a mut means that the reference is valid for exactly 'a which means that its referent must have been created before 'a . &'a mut表示引用对'a完全有效,这意味着它的引用对象必须在'a之前创建。

These two requirements almost contradict each other, so they are practically impossible to satisfy (unless 'a: 'static ).这两个要求几乎相互矛盾,因此几乎不可能满足(除非'a: 'static )。 The general fix for this is to avoid over-constraining the lifetimes, by using separate lifetime parameters:对此的一般解决方法是通过使用单独的生命周期参数来避免过度约束生命周期:

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

However, this won't work in your case, because there's a deeper problem, hinted at by the <'e, ???> : by having a chain of &mut references, we're allowing the holder of those references to modify any part of any level of that chain , and in particular to replace those references with different references.但是,这在您的情况下不起作用,因为<'e, ???>暗示了一个更深层次的问题:通过具有&mut引用链,我们允许这些引用的持有者修改任何该链的任何级别的一部分,特别是用不同的引用替换这些引用。 But in order to do that, they would need to replace it with something that has the same lifetime — and the shape of this structure is a chain of nested longer-as-you-go-farther lifetimes, so for example, taking two Environment s and mutating them to swap their positions is possible by the shape of the structure, but would violate the lifetimes.但为了做到这一点,他们需要用具有相同生命周期的东西来替换它——而这种结构的形状是一串嵌套的越长越远生命周期,例如,取两个Environment s 并通过结构的形状改变它们以交换它们的位置是可能的,但会违反生命周期。

Something that is possible, but not as flexible as you might want, is to use immutable references inside the structure.一种可能但不像您想要的那样灵活的方法是在结构中使用不可变引用。 To demonstrate this, here's a simple scope checker:为了证明这一点,这里有一个简单的 scope 检查器:

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(())
    );
}

Note that the declaration注意声明

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

contains Option<&'r Environment<'r>> , which is exactly what I told you not to do earlier.包含Option<&'r Environment<'r>> ,这正是我之前告诉你不要做的。 The reason that Environment<'r> doesn't over-constrain the lifetimes is that the type of the referent of an immutable reference is covariant — this means that if we want an Environment<'r> we can accept an Environment<'e> as long as 'e outlives 'r . Environment<'r>没有过度约束生命周期的原因是不可变引用的引用对象的类型是协变的——这意味着如果我们想要一个Environment<'r>我们可以接受一个Environment<'e>只要'e'r更长寿。 Mutable references, on the other hand, require invariance : they must exactly match.另一方面,可变引用需要不变性:它们必须完全匹配。 (This is because with an immutable reference, data can only flow out of it, but given a mutable reference, data can flow either in or out, so it would be unsound if there was a lifetime mismatch in either direction.) (这是因为对于不可变引用,数据只能从中流出,但给定可变引用,数据可以流入或流出,因此如果在任一方向存在生命周期不匹配,则将是不合理的。)

The caveat with what I've written above is that it is not possible to mutate environments farther up the chain, which you might want if you're performing type inference (so that uses can determine the types of declarations).我上面写的需要注意的是,不可能在链上更远的地方改变环境,如果你正在执行类型推断,你可能想要这样做(以便使用可以确定声明的类型)。 I'm not entirely certain, but I think that in order to do that, you'll need to resort to interior mutability to let you mutate something even without a mutable reference.我不完全确定,但我认为为了做到这一点,你需要诉诸内部可变性,让你在没有可变引用的情况下改变某些东西。

Here's the above example modified to use the interior mutability tool std::cell::RefCell .这是上面的示例修改为使用内部可变性工具std::cell::RefCell Note that this code does not actually use the additional flexibility — but it's there;请注意,此代码实际上并没有使用额外的灵活性——但它就在那里; you can modify the symbols of any parent by using the explicit runtime-checked .symbols.borrow_mut() operation.您可以使用经过运行时检查的显式.symbols.borrow_mut()操作来修改任何父代的symbols

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(())
    );
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM