在Rust里访问所有者的数据
原文¶
https://zhuanlan.zhihu.com/p/25602228
在C/C++里,我们经常做的一件事是,把一个复杂的对象按逻辑拆分成几个部分(组件),然后实现各自的功能,对不对?接下来要面临的一个问题是,有的时候某个组件还需要获取来自于其他组件或者整体的信息。这个时候你会怎么做呢?其中一个做法,就是在组件里存储一个整体对象的指针,用来从局部状态出发,回头获取整体对象的状态。
在Rust里面临的问题也是一样的。有的时候,A是B的所有者,然而B的极少数功能可能就需要从A获取数据,向A发送通知,之类的事情。那么问题来了。在Rust里我们平时不会像C/C++那样在B里面存储一个A的借用(试试看,你会弄的一团糟)或者指针。存储指针其实是不安全的,因为A是可能随时被move走的,会导致你的指针失效。
那么怎么办呢?
其实这个操作在Rust的概念模型下是直接可以实现的,甚至不需要unsafe,但是需要一点辅助代码作为通用模板。诀窍就是:只要保持总体的借用一直都在就可以了。
我刚才试着写了几行代码来完成这件事。
use std::ops::{Deref, DerefMut};
pub struct ChildRef<'b, O: ?Sized + 'b, T: ?Sized> {
owner_ref: &'b O,
accessor: fn(&O) -> &T,
}
impl<'b, O: ?Sized, T: ?Sized> ChildRef<'b, O, T> {
pub fn new(o: &'b O, f: fn(&O) -> &T) -> Self {
ChildRef {
owner_ref: o,
accessor: f,
}
}
pub fn owner(&self) -> &O {
self.owner_ref
}
}
impl<'b, O: ?Sized, T: ?Sized> Deref for ChildRef<'b, O, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
let acc = self.accessor.clone();
acc(self.owner_ref)
}
}
pub struct ChildMut<'b, O: ?Sized + 'b, T: ?Sized> {
owner_ref: &'b mut O,
accessor: fn(&O) -> &T,
accessor_mut: fn(&mut O) -> &mut T,
}
impl<'b, O: ?Sized, T: ?Sized> ChildMut<'b, O, T> {
pub fn new(o: &'b mut O, f: fn(&O) -> &T, f_mut: fn(&mut O) -> &mut T) -> Self {
ChildMut {
owner_ref: o,
accessor: f,
accessor_mut: f_mut,
}
}
pub fn owner(&self) -> &O {
self.owner_ref
}
pub fn owner_mut(&mut self) -> &mut O {
self.owner_ref
}
}
impl<'b, O: ?Sized, T: ?Sized> Deref for ChildMut<'b, O, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
let acc = self.accessor.clone();
acc(self.owner_ref)
}
}
impl<'b, O: ?Sized, T: ?Sized> DerefMut for ChildMut<'b, O, T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
let acc = self.accessor_mut.clone();
acc(self.owner_ref)
}
}
这里实现了一套指针,一个称为ChildRef,一个称为ChildMut,分别对应于不变、可变两种情况。
接下来举个使用的例子好了。我有两个struct,Container里拥有一个Item对象。
Container上面有读写i的方法(实现里有辅助函数):
impl Container {
fn new() -> Self {
Container {
i: Item { data: 42usize },
m: 100usize,
}
}
fn get_item(&self) -> ChildRef<Container, Item> {
fn acc(s: &Container) -> &Item {
&s.i
}
ChildRef::new(self, acc)
}
fn get_item_mut(&mut self) -> ChildMut<Container, Item> {
fn acc(s: &Container) -> &Item {
&s.i
}
fn acc_mut(s: &mut Container) -> &mut Item {
&mut s.i
}
ChildMut::new(self, acc, acc_mut)
}
}
假设Item的result方法需要从Container上面获取m的值,与自己的data相加,返回结果。这个时候,我们把这个方法实现在ChildRef上。注意owner()方法使我们可以获取Container对象的引用。
现在我们就可以像在C/C++里一样畅通无阻了~(中间可以加无数个.owner().get_item()哦)
结果是142。