如何使用Arc和Weak创建循环引用?

时间:2018-05-19 13:40:42

标签: reference rust smart-pointers

我有两个结构:

struct A { 
    map: HashMap<u32, Vec<B>>,
}

struct B {
    weak: Weak<A>
}

构建A时,它将拥有多个B,每个A链接回刚刚构建的let a = Arc::new(A { map: HashMap::new() }); let b1 = B { weak: Arc::downgrade(&a) }; let b3 = B { weak: Arc::downgrade(&a) }; let b2 = B { weak: Arc::downgrade(&a) }; a.map.insert(5, vec![b1, b2]); a.map.insert(10, vec![b3]); ,类似于:

Arc

Playground

这不起作用,因为Weak没有提供修改地图的方法。 Arc::get_mut不起作用,因为A已经构造为值。

如何使用B来构建map?我在访问{{1}}时试图避免运行时检查,因为在构造之后它永远不会再被修改。我使用不安全的代码或批准的夜间功能没有问题。

2 个答案:

答案 0 :(得分:5)

实际上,我会从相反的方向接近这一点。

HashMap是一种比Weak<T: Sized>更复杂的类型,因此在事后交换Weak要容易得多。因此,我的方法是:

  1. 使用虚拟参考创建B
  2. 创建A,转让B
  3. 的所有权
  4. B进行迭代,将A换成真实的。{/ li>

    AFAIK,标准库没有提供任何方法(1)创建null Weak和(2)原子地交换它们。 Crossbeam有ArcCell的示例:只需按Arc搜索/替换所有Weak即可WeakCell

    我们可以使用WeakCell<T>

    #[derive(Default)]
    struct A { 
        map: HashMap<u32, Vec<B>>,
    }
    
    struct B {
        weak: WeakCell<A>,
    }
    
    impl A {
        pub fn new(map: HashMap<u32, Vec<B>>) -> Arc<A> {
            let a = Arc::new(A { map });
            let weak = Arc::downgrade(&a);
            for (_, bs) in &a.map {
                for b in bs {
                    b.weak.set(weak.clone());
                }
            }
            a
        }
    }
    
    impl B {
        pub fn new(a: &Arc<A>) -> B { B { weak: WeakCell::new(Arc::downgrade(a)), } }
    }
    
    fn main() {
        let dummy = Arc::new(A::default());
    
        let (b1, b2, b3) = (B::new(&dummy), B::new(&dummy), B::new(&dummy));
    
        let mut map = HashMap::new();
        map.insert(5, vec![b1, b2]);
        map.insert(10, vec![b3]);
    
        let _a = A::new(map);
    
        //  Do something!
    }
    

    您可以在行动中看到on the playground

    应该可以修改WeakCell以从0构造它(保证它将在稍后初始化),从而避免需要虚拟引用。这是一个留给读者的练习;)

答案 1 :(得分:4)

如果您现有Arc::get_mut()引用,则

Weak将失败,因此您需要考虑使用内部可变性。由于您使用的是Arc,因此我假设您处于多线程环境中,因此我将使用线程安全的RwLock

use std::sync::{Arc, Weak, RwLock};
use std::collections::HashMap;

struct A { 
    map: RwLock<HashMap<u32, Vec<B>>>,
}

struct B {
    weak: Weak<A>
}

现在你可以构建这样的对象:

fn init_a(a: Arc<A>) -> Arc<A> {
    let b1 = B { weak: Arc::downgrade(&a) };
    let b2 = B { weak: Arc::downgrade(&a) };
    // extra block is required so that the Mutex's write lock is dropped 
    // before we return a
    {
        let mut map = a.map.write().unwrap();
        let vec = map.entry(0).or_insert(Vec::new());
        vec.push(b1);
        vec.push(b2);
    }
    a
}

fn main() {
    let mut a = Arc::new(A { map: RwLock::new(HashMap::new()) });
    a = init_a(a);
}

如果确实希望摆脱Mutex的所有运行时开销,并且您不介意使用unsafe代码,则可以使用{ {1}}。它的开销为零,但其界面需要一个UnsafeCell块,而且它在代码中是一个额外的解包层。另外unsafe不是UnsafeCell,因此您无法在线程之间共享它。

要解决这些问题,通过确保在构建过程中只需要考虑Sync,您就可以利用UnsafeCell零大小成本且不影响布局的事实。不使用UnsafeCell,而是使用其他类型进行构建,除了A之外,它与A相同。然后,这些类型可以与UnsafeCell互换使用。

mem::transmute

你也可以用原始指针做到这一点,但我觉得有点安全&#34;与use std::collections::HashMap; use std::sync::{Arc, Weak}; use std::cell::UnsafeCell; use std::mem; struct A { map: HashMap<u32, Vec<B>>, } struct B { weak: Weak<A> } impl A { fn new() -> Arc<A> { let a = A { map: HashMap:: new() }; Self::init_a(Arc::new(a)) } fn init_a(a: Arc<A>) -> Arc<A> { // Important: The layout is identical to A struct AConstruct { map: UnsafeCell<HashMap<u32, Vec<B>>>, } // Treat the object as if was an AConstruct instead let a: Arc<AConstruct> = unsafe { mem::transmute(a) }; let map = unsafe { &mut *a.map.get() }; // B's weak references are to Arc<A> not to Arc<AConstruct> let weak_a: Weak<A> = unsafe { mem::transmute(Arc::downgrade(&a)) }; // Actual initialization here let vec = map.entry(0).or_insert(Vec::new()); let b1 = B { weak: weak_a.clone() }; let b2 = B { weak: weak_a.clone() }; vec.push(b1); vec.push(b2); // We're done. Pretend the UnsafeCells never existed unsafe { mem::transmute(a) } } } !当LLVM保证某些数据是不可变的时,LLVM会做一些优化,UnsafeCell会在它破坏这些保证时为你提供保护。因此,我并非100%确定这样做的安全性:

UnsafeCell