一个类型可以知道何时可变借用已经结束了吗?

时间:2017-12-04 18:43:02

标签: rust

我有一个结构体,每次可变借位结束时,我都想调用结构体的一个方法。要做到这一点,我需要知道什么时候对它的可变借用已被删除。如何才能做到这一点?

2 个答案:

答案 0 :(得分:3)

免责声明:下面的答案描述了一个可能的解决方案,但它不是一个非常好的解决方案,如Sebastien Redl的评论所述:

  

[T]他是尝试维护不变量的方式。主要是因为删除引用可以使用mem::forget来抑制。这对于RefCell来说很好,如果你不放弃引用,你最终会因为没有释放动态借用而最终恐慌,但是如果违反“分数最短形式”不变则不好导致奇怪的结果或微妙的性能问题,如果你需要保持“线程不在当前范围内的变量”不变,这是灾难性的。

然而,可以使用临时结构作为“临时区域”,在它被删除时更新引用区域,从而正确地保持不变量;然而,该版本基本上相当于制作一个合适的包装类型和一种奇怪的方式来使用它。解决这个问题的最好方法是通过一个不透明的包装器结构,除了通过明确保持不变量的方法之外,它不会暴露其内部结构。

不用多说,原来的答案是:

完全 ...但非常接近。我们可以使用RefCell<T>作为模型来完成此操作。这是一个抽象的问题,但我会用一个具体的例子来证明。 (这不是完整的示例,而是显示一般原则的东西。)

假设你想要制作一个总是最简单形式的Fraction结构(完全缩小,例如3/5而不是6/10)。您编写了一个包含裸数据的结构RawFractionRawFraction个实例 总是以最简单的形式存在,但它们有一个方法fn reduce(&mut self)可以减少它们。

现在你需要一个智能指针类型,你总是会用它来改变RawFraction,它在被删除时调用指向结构上的.reduce()。我们称之为RefMut,因为这是RefCell使用的命名方案。您在其上实施Deref<Target = RawFraction>DerefMutDrop,如下所示:

pub struct RefMut<'a>(&'a mut RawFraction);

impl<'a> Deref for RefMut<'a> {
    type Target = RawFraction;
    fn deref(&self) -> &RawFraction {
        self.0
    }
}

impl<'a> DerefMut for RefMut<'a> {
    fn deref_mut(&mut self) -> &mut RawFraction {
        self.0
    }
}

impl<'a> Drop for RefMut<'a> {
    fn drop(&mut self) {
        self.0.reduce();
    }
}

现在,只要您有RefMutRawFraction并放弃它,就会知道RawFraction之后会以最简单的形式出现。您此时需要做的就是确保RefMut是{em>唯一方式获取&mut RawFraction部分Fraction部分

pub struct Fraction(RawFraction);

impl Fraction {
    pub fn new(numerator: i32, denominator: i32) -> Self {
        // create a RawFraction, reduce it and wrap it up
    }

    pub fn borrow_mut(&mut self) -> RefMut {
        RefMut(&mut self.0)
    }
}

注意pub标记(缺少标记):我正在使用它们来确保暴露界面的健全性。所有这三种类型都应该自己放在一个模块中。在RawFraction内标记pub字段Fraction是不正确的,因为那时(对于模块外部的代码)可以创建未减少的Fraction而不使用{ {1}}或获取new而不通过&mut RawFraction

假设所有这些代码都放在一个名为RefMut的模块中,您可以使用它(假设frac实现Fraction):

Display

这些类型对不变量进行编码:无论您拥有let f = frac::Fraction::new(3, 10); println!("{}", f); // prints 3/10 f.borrow_mut().numerator += 3; println!("{}", f); // prints 3/5 ,您都可以知道它已完全缩小。如果您有FractionRawFraction等,则无法确定。如果您愿意,也可以将&RawFraction的字段设为非RawFraction,这样您就无法在所有中获得未减少的分数,除非通过调用{{1} } pub

答案 1 :(得分:2)

基本上同样的事情在RefCell完成。在那里,您希望在借用结束时减少运行时借用计数。在这里,您想要执行任意操作。

因此,让我们重新使用编写一个返回包装引用的函数的概念:

struct Data {
    content: i32,
}

impl Data {
    fn borrow_mut(&mut self) -> DataRef {
        println!("borrowing");
        DataRef { data: self }
    }

    fn check_after_borrow(&self) {
        if self.content > 50 {
            println!("Hey, content should be <= {:?}!", 50); 
        }
    }
}

struct DataRef<'a> {
    data: &'a mut Data
}

impl<'a> Drop for DataRef<'a> {
    fn drop(&mut self) {
        println!("borrow ends");
        self.data.check_after_borrow()
    }
}

fn main() {
    let mut d = Data { content: 42 };    
    println!("content is {}", d.content);    
    {
        let b = d.borrow_mut();
        //let c = &d;  // Compiler won't let you have another borrow at the same time
        b.data.content = 123;
        println!("content set to {}", b.data.content);
    }  // borrow ends here
    println!("content is now {}", d.content);
}

这导致以下输出:

content is 42
borrowing
content set to 123
borrow ends
Hey, content should be <= 50!
content is now 123

请注意,您仍然可以获得未经检查的可变借款,例如: let c = &mut d;。这将在不调用check_after_borrow的情况下以静默方式删除。