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
код, вы можете использовать UnsafeCell
.У него нулевые накладные расходы, но для его интерфейса требуется блок unsafe
, и это дополнительный уровень развертывания в вашем коде.Кроме того, UnsafeCell
не является Sync
, поэтому вы не можете поделиться им между потоками.
Чтобы решить эти проблемы, убедившись, что вам нужно учитывать только UnsafeCell
во время построения, вы можете взятьПреимущество в том, что UnsafeCell
имеет нулевой размер и не влияет на макет.Вместо A
используйте другой тип построения, который идентичен A
, за исключением UnsafeCell
.Эти типы могут затем использоваться взаимозаменяемо с mem::transmute
.
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) }
}
}
Вы можете также сделать это с необработанными указателями, но я чувствую себя немного более безопасным с UnsafeCell
!LLVM выполняет некоторую оптимизацию, когда ему дается гарантия того, что определенные данные неизменны, а UnsafeCell
делает некоторую магию, чтобы защитить вас, когда нарушает эти гарантии.Так что я не уверен на 100% в безопасности:
fn init_a(a: Arc<A>) -> Arc<A> {
// Raw IMMUTABLE pointer to the HashMap
let ptr = &a.map as *const HashMap<_, _>;
// Unsafely coerce it to MUTABLE
let map: &mut HashMap<_, _> = unsafe { mem::transmute(ptr) };
let weak_a: Weak<A> = 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);
a
}