与内部可变性作斗争

Posted

技术标签:

【中文标题】与内部可变性作斗争【英文标题】:Struggling with interior mutability 【发布时间】:2019-02-10 15:50:19 【问题描述】:

我有这样的数据结构:

struct R 
    hmhs: HashMap<i64, HashSet<i64>>,


impl R 
    fn hs_for_hmhs(&mut self) -> &mut HashSet<i64> 
        if let None = self.hmhs.get(&0) 
            self.hmhs.insert(0, HashSet::new());
        

        self.hmhs.get_mut(&0).unwrap()
    

    fn iter_for_hmhs<'a>(&'a mut self) -> impl Iterator<Item = &'a i64> 
        self.hs_for_hmhs().iter()
    

    fn insert_for_hmhs(&mut self, i: i64) -> bool 
        self.hs_for_hmhs().insert(i)
    

这似乎可行,但所有方法都需要一个可变的 参考self,这是不幸的。我试着给 内部可变性一展身手:

struct S 
    hmhs: RefCell<HashMap<i64, HashSet<i64>>>,


impl S 
    fn hs_for_hmhs(&self) -> &HashSet<i64> 
        if let None = self.hmhs.borrow().get(&0) 
            self.hmhs.borrow_mut().insert(0, HashSet::new());
        

        self.hmhs.borrow_mut().get_mut(&0).unwrap()
    

    fn iter_for_hmhs(&mut self) -> impl Iterator<Item = &i64> 
        self.hs_for_hmhs().iter()
    

    fn insert_for_hmhs(&mut self, i: i64) -> bool 
        self.hs_for_hmhs().insert(i)
    

但是,我似乎经常遇到问题。大多是How do I return a reference to something inside a RefCell without breaking encapsulation?的各种@

我在这里尝试了很多变体,但我遗漏了一些东西 在我的理解中是基本的。有没有办法实现我的目标 想要吗?

Complete Code:

use std::cell::RefCell;
use std::collections::HashMap, HashSet;

struct R 
    hmhs: HashMap<i64, HashSet<i64>>,


impl R 
    fn hs_for_hmhs(&mut self) -> &mut HashSet<i64> 
        if let None = self.hmhs.get(&0) 
            self.hmhs.insert(0, HashSet::new());
        

        self.hmhs.get_mut(&0).unwrap()
    

    fn iter_for_hmhs<'a>(&'a mut self) -> impl Iterator<Item = &'a i64> 
        self.hs_for_hmhs().iter()
    

    fn insert_for_hmhs(&mut self, i: i64) -> bool 
        self.hs_for_hmhs().insert(i)
    


struct S 
    hmhs: RefCell<HashMap<i64, HashSet<i64>>>,


impl S 
    fn hs_for_hmhs(&self) -> &mut HashSet<i64> 
        if let None = self.hmhs.borrow().get(&0) 
            self.hmhs.borrow_mut().insert(0, HashSet::new());
        

        self.hmhs.borrow_mut().get_mut(&0).unwrap()
    

    fn iter_for_hmhs(&self) -> impl Iterator<Item = &i64> 
        self.hs_for_hmhs().iter()
    

    fn insert_for_hmhs(&self, i: i64) -> bool 
        self.hs_for_hmhs().insert(i)
    


fn main() 

编译器消息:

error[E0597]: borrowed value does not live long enough
  --> src/main.rs:36:9
   |
36 |         self.hmhs.borrow_mut().get_mut(&0).unwrap()
   |         ^^^^^^^^^^^^^^^^^^^^^^ temporary value does not live long enough
37 |     
   |     - temporary value only lives until here
   |
note: borrowed value must be valid for the anonymous lifetime #1 defined on the method body at 31:5...
  --> src/main.rs:31:5
   |
31 | /     fn hs_for_hmhs(&self) -> &mut HashSet<i64> 
32 | |         if let None = self.hmhs.borrow().get(&0) 
33 | |             self.hmhs.borrow_mut().insert(0, HashSet::new());
34 | |         
35 | |
36 | |         self.hmhs.borrow_mut().get_mut(&0).unwrap()
37 | |     
   | |_____^

【问题讨论】:

为什么需要可变借用?所有方法都返回对其内容的不可变引用? 哪个? HashMap 和第二个 borrow_mut 调用。我不知道。如果我不这样做,它会抱怨更多。 啊,我没注意到insert_for_hmhs 调用insert 哦,HashSet -- 是的,这部分将实现结构的公共接口。 HashMap 确实是一个实现细节。 这是一个棘手的问题,我想知道您是否应该尝试不同的方法。例如,this 将让你成功——一个抽象的智能指针指向HashSet。但是很难将迭代器返回给它。 【参考方案1】:

我找到了一个解决方案——将HashMap 提取为原始指针。这反过来意味着我可以在没有包括返回迭代器在内的恶作剧的情况下访问HashSet

我对这个作为解决方案感到非常满意。不安全代码很小且包含在内,如果我理解编译器在没有不安全的情况下抱怨的原因,则它不会出现在此代码中,因为HashMapHashSet 在构造后都不会被删除或替换。

这是一个很大的努力。

use std::cell::RefCell;
use std::collections::HashMap, HashSet;

struct R 
    hmhs: HashMap<i64, HashSet<i64>>,


impl R 
    fn hs_for_hmhs(&mut self) -> &mut HashSet<i64> 
        if let None = self.hmhs.get(&0) 
            self.hmhs.insert(0, HashSet::new());
        

        self.hmhs.get_mut(&0).unwrap()
    

    fn iter_for_hmhs<'a>(&'a mut self) -> impl Iterator<Item = &'a i64> 
        self.hs_for_hmhs().iter()
    

    fn insert_for_hmhs(&mut self, i: i64) -> bool 
        self.hs_for_hmhs().insert(i)
    


struct S 
    hmhs: RefCell<HashMap<i64, HashSet<i64>>>,


impl S 
    fn hs_as_ptr(&self) -> *mut HashMap<i64, HashSet<i64>> 
        self.hmhs.borrow_mut().entry(0).or_insert(HashSet::new());
        self.hmhs.as_ptr()
    

    fn mut_hs_for_hmhs(&mut self) -> &mut HashSet<i64> 
        unsafe  (*self.hs_as_ptr()).get_mut(&0).unwrap() 
    
    fn hs_for_hmhs(&self) -> &HashSet<i64> 
        unsafe  (*self.hs_as_ptr()).get(&0).unwrap() 
    

    fn iter_for_hmhs<'a>(&'a self) -> impl Iterator<Item = &'a i64> + 'a 
        self.hs_for_hmhs().iter()
    

    fn insert_for_hmhs(&mut self, i: i64) -> bool 
        self.mut_hs_for_hmhs().insert(i)
    


fn main() 
    let mut r = R 
        hmhs: HashMap::new(),
    ;
    let mut s = S 
        hmhs: RefCell::new(HashMap::new()),
    ;

    r.insert_for_hmhs(10);
    s.insert_for_hmhs(20);

    println!("r next: :?", r.iter_for_hmhs().next());
    println!("s next: :?", s.iter_for_hmhs().next());

https://play.rust-lang.org/?gist=3ed1977bdd5f9f82d144fe128f618979&version=stable&mode=debug&edition=2015

【讨论】:

以上是关于与内部可变性作斗争的主要内容,如果未能解决你的问题,请参考以下文章

与 OOP 概念作斗争

在可可中与货币作斗争

与意外的交叉线程和停止作斗争

与 OkHttp 拦截器作斗争

与 OCR 逆向工程作斗争

使用内部可变性模式的“BorrowMutError”