我有一个结构体,每次可变借位结束时,我都想调用结构体的一个方法。要做到这一点,我需要知道什么时候对它的可变借用已被删除。如何才能做到这一点?
答案 0 :(得分:3)
免责声明:下面的答案描述了一个可能的解决方案,但它不是一个非常好的解决方案,如Sebastien Redl的评论所述:
[T]他是尝试维护不变量的坏方式。主要是因为删除引用可以使用
mem::forget
来抑制。这对于RefCell
来说很好,如果你不放弃引用,你最终会因为没有释放动态借用而最终恐慌,但是如果违反“分数最短形式”不变则不好导致奇怪的结果或微妙的性能问题,如果你需要保持“线程不在当前范围内的变量”不变,这是灾难性的。
然而,可以使用临时结构作为“临时区域”,在它被删除时更新引用区域,从而正确地保持不变量;然而,该版本基本上相当于制作一个合适的包装类型和一种奇怪的方式来使用它。解决这个问题的最好方法是通过一个不透明的包装器结构,除了通过明确保持不变量的方法之外,它不会暴露其内部结构。
不用多说,原来的答案是:
不完全 ...但非常接近。我们可以使用RefCell<T>
作为模型来完成此操作。这是一个抽象的问题,但我会用一个具体的例子来证明。 (这不是完整的示例,而是显示一般原则的东西。)
假设你想要制作一个总是最简单形式的Fraction
结构(完全缩小,例如3/5而不是6/10)。您编写了一个包含裸数据的结构RawFraction
。 RawFraction
个实例 总是以最简单的形式存在,但它们有一个方法fn reduce(&mut self)
可以减少它们。
现在你需要一个智能指针类型,你总是会用它来改变RawFraction
,它在被删除时调用指向结构上的.reduce()
。我们称之为RefMut
,因为这是RefCell
使用的命名方案。您在其上实施Deref<Target = RawFraction>
,DerefMut
和Drop
,如下所示:
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();
}
}
现在,只要您有RefMut
到RawFraction
并放弃它,就会知道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
,您都可以知道它已完全缩小。如果您有Fraction
,RawFraction
等,则无法确定。如果您愿意,也可以将&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
的情况下以静默方式删除。