我有两个结构:
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
这不起作用,因为Weak
没有提供修改地图的方法。 Arc::get_mut
不起作用,因为A
已经构造为值。
如何使用B
来构建map
?我在访问{{1}}时试图避免运行时检查,因为在构造之后它永远不会再被修改。我使用不安全的代码或批准的夜间功能没有问题。
答案 0 :(得分:5)
实际上,我会从相反的方向接近这一点。
HashMap
是一种比Weak<T: Sized>
更复杂的类型,因此在事后交换Weak
要容易得多。因此,我的方法是:
B
。A
,转让B
。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