在Rust里访问所有者的数据

原文

在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对象。

struct Item {
    pub data: usize,
}

struct Container {
    i: Item,
    pub m: usize,
}

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对象的引用。

impl<'b> ChildRef<'b, Container, Item> {
    fn result(&self) -> usize {
        self.owner().m + self.data
    }
}

现在我们就可以像在C/C++里一样畅通无阻了~(中间可以加无数个.owner().get_item()哦)

fn main() {
    let c = Container::new();
    println!{"{}", c.get_item().owner().get_item().result() };
}

结果是142。

Last modification:March 6th, 2021 at 12:13 am
安安,亲觉得有用, 请随意打赏嗷