如何在不破坏封装的情况下返回对RefCell内部内容的引用?

我有一个具有内部可变性的结构。


use std::cell::RefCell;


struct MutableInterior {

    hide_me: i32,

    vec: Vec<i32>,

}

struct Foo {

    //although not used in this particular snippet,

    //the motivating problem uses interior mutability

    //via RefCell.

    interior: RefCell<MutableInterior>,

}


impl Foo {

    pub fn get_items(&self) -> &Vec<i32> {

        &self.interior.borrow().vec

    }

}


fn main() {

    let f = Foo {

        interior: RefCell::new(MutableInterior {

            vec: Vec::new(),

            hide_me: 2,

        }),

    };

    let borrowed_f = &f;

    let items = borrowed_f.get_items();

}

产生错误:


error[E0597]: borrowed value does not live long enough

  --> src/main.rs:16:10

   |

16 |         &self.interior.borrow().vec

   |          ^^^^^^^^^^^^^^^^^^^^^^ temporary value does not live long enough

17 |     }

   |     - temporary value only lives until here

   |

note: borrowed value must be valid for the anonymous lifetime #1 defined on the method body at 15:5...

  --> src/main.rs:15:5

   |

15 | /     pub fn get_items(&self) -> &Vec<i32> {

16 | |         &self.interior.borrow().vec

17 | |     }

   | |_____^

问题是我无法在该函数上Foo返回借用vec,因为借用vec仅在的有效期内有效Ref,但Ref立即超出范围。


我认为Ref必须坚持,因为:


RefCell<T>使用Rust的生命周期来实现“动态借用”,这一过程使人们可以要求对内部价值进行临时,排他,可变的访问。RefCell<T>s的借记是在“运行时”进行跟踪的,这与Rust的本机引用类型不同,后者在编译时是完全静态跟踪的。由于RefCell<T>借贷是动态的,因此可以尝试借入已经可变借贷的值;当这种情况发生时,将导致任务恐慌。


现在,我可以改写这样的函数来返回整个内部:


pub fn get_mutable_interior(&self) -> std::cell::Ref<MutableInterior>;

但是,这可能会将字段(MutableInterior.hide_me在此示例中)暴露为真正的私有实现细节Foo。


理想情况下,我只想公开vec自身,可能需要配备警卫来实现动态借用行为。然后,呼叫者不必了解hide_me。


慕斯709654
浏览 631回答 3
3回答

有只小跳蛙

您可以创建一个与所Ref<'a,T>返回的后卫类似的新结构RefCell::borrow(),以对其进行包装Ref并避免其超出范围,例如:use std::cell::Ref;struct FooGuard<'a> {&nbsp; &nbsp; guard: Ref<'a, MutableInterior>,}然后,您可以Deref为其实现特征,以便可以像使用它一样使用它&Vec<i32>:use std::ops::Deref;impl<'b> Deref for FooGuard<'b> {&nbsp; &nbsp; type Target = Vec<i32>;&nbsp; &nbsp; fn deref(&self) -> &Vec<i32> {&nbsp; &nbsp; &nbsp; &nbsp; &self.guard.vec&nbsp; &nbsp; }}之后,更新您的get_items()方法以返回FooGuard实例:impl Foo {&nbsp; &nbsp; pub fn get_items(&self) -> FooGuard {&nbsp; &nbsp; &nbsp; &nbsp; FooGuard {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; guard: self.interior.borrow(),&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}并Deref做魔术:fn main() {&nbsp; &nbsp; let f = Foo {&nbsp; &nbsp; &nbsp; &nbsp; interior: RefCell::new(MutableInterior {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; vec: Vec::new(),&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hide_me: 2,&nbsp; &nbsp; &nbsp; &nbsp; }),&nbsp; &nbsp; };&nbsp; &nbsp; let borrowed_f = &f;&nbsp; &nbsp; let items = borrowed_f.get_items();&nbsp; &nbsp; let v: &Vec<i32> = &items;}

函数式编程

您可以使用Ref::map(从Rust 1.8开始)而不是创建全新的类型。这与Levans的现有答案具有相同的结果:use std::cell::Ref;impl Foo {&nbsp; &nbsp; pub fn get_items(&self) -> Ref<'_, Vec<i32>> {&nbsp; &nbsp; &nbsp; &nbsp; Ref::map(self.interior.borrow(), |mi| &mi.vec)&nbsp; &nbsp; }}您还可以使用新功能,例如从API中impl Trait隐藏Ref:use std::cell::Ref;use std::ops::Deref;impl Foo {&nbsp; &nbsp; pub fn get_items(&self) -> impl Deref<Target = Vec<i32>> + '_ {&nbsp; &nbsp; &nbsp; &nbsp; Ref::map(self.interior.borrow(), |mi| &mi.vec)&nbsp; &nbsp; }}

侃侃尔雅

你可以包装Vec在Rc。use std::cell::RefCell;use std::rc::Rc;struct MutableInterior {&nbsp; &nbsp; hide_me: i32,&nbsp; &nbsp; vec: Rc<Vec<i32>>,}struct Foo {&nbsp; &nbsp; interior: RefCell<MutableInterior>,}impl Foo {&nbsp; &nbsp; pub fn get_items(&self) -> Rc<Vec<i32>> {&nbsp; &nbsp; &nbsp; &nbsp; self.interior.borrow().vec.clone() // clones the Rc, not the Vec&nbsp; &nbsp; }}fn main() {&nbsp; &nbsp; let f = Foo {&nbsp; &nbsp; &nbsp; &nbsp; interior: RefCell::new(MutableInterior {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; vec: Rc::new(Vec::new()),&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hide_me: 2,&nbsp; &nbsp; &nbsp; &nbsp; }),&nbsp; &nbsp; };&nbsp; &nbsp; let borrowed_f = &f;&nbsp; &nbsp; let items = borrowed_f.get_items();}当您需要对进行突变时Vec,可使用Rc::make_mut获取对的可变引用Vec。如果还有其他的Rc小号指的是Vec,make_mut将分离的Rc与其他RcS,克隆Vec和更新本身指的是新的Vec,然后给你一个可变的引用。这样可以确保其他Rcs中的值不会突然改变(因为Rc它本身不提供内部可变性)。
打开App,查看更多内容
随时随地看视频慕课网APP