Expand description
A trait for universal reference-counted lock
with interior mutability
allowing multiple readers or a single writer,
which may represent either Rc<RefCell<T>> or Arc<RwLock<T>>.
§Basics
§Motivation
The Rc<RefCell<T>> and Arc<RwLock<T>> are idiomatic ways of expressing
interior mutability in Rust in the single-threaded and multi-threaded
scenarious respectively. Both types provide essentially the same interface
with multiple readers or a single writer for their managed data.
However, despite their similarity, these types do not share any common trait
and thus can’t be used in a generic way: one can’t define a function argument
or a struct field that could accept either Rc<RefCell<T>> or Arc<RwLock<T>>.
This leads to code duplication if it is not known in advance which kind of
smart pointer (Rc or Arc) will be passed.
UniRcLock solves this problem by providing a common trait
for Rc<RefCell<T>> and Arc<RwLock<T>> so that they could be used in
a generic way in single-threaded and multi-threaded scenarious alike.
§Performance
UniRcLock is a zero-cost abstraction.
§Limitations
An ability to recover from lock poisoning in RwLock<T> is lost
when using UniRcLock. The methods read() and write() will panic if
the lock is poisoned.
§Examples
A generic function which accepts both Rc<RefCell<T>> and Arc<RwLock<T>>:
#[derive(Debug)]
struct Foo(i32);
fn incr_foo(v: impl UniRcLock<Foo>){
v.write().0 += 1;
}
// Using Rc
let ptr1 = Rc::new(RefCell::new(Foo(0)));
incr_foo(ptr1.clone());
println!("After increment: {:?}", ptr1);
// Using Arc
let ptr2 = Arc::new(RwLock::new(Foo(0)));
incr_foo(ptr2.clone());
println!("After increment: {:?}", ptr2);Example of generic struct, which can hold either Rc<RefCell<T>> or Arc<RwLock<T>>:
// A user struct
struct State {val: i32}
// Generic wrapper
#[derive(Debug,Clone)]
struct StateHandler<T: UniRcLock<State>> {
state: T,
}
impl<T: UniRcLock<State>> StateHandler<T> {
// Constructor taking either Rc<RefCell<T>>` or `Arc<RwLock<T>>
fn new(val: T) -> Self {
Self{state: val}
}
}
// Using with Rc
{
let st = Rc::new(RefCell::new(State { val: 42 }));
let st_handler = StateHandler::new(st);
st_handler.state.write().val += 1;
println!("{}", st_handler.state.read().val);
}
// Using with Arc in exactly the same way
{
let st = Arc::new(RwLock::new(State { val: 42 }));
let st_handler = StateHandler::new(st);
st_handler.state.write().val += 1;
println!("{}", st_handler.state.read().val);
}
// Using in multiple threads with Arc
{
let st = Arc::new(RwLock::new(State { val: 42 }));
let st_handler = StateHandler::new(st);
let threads: Vec<_> = (0..10)
.map(|i| {
let h = st_handler.clone();
thread::spawn(move || {
h.state.write().val += 1;
println!("Thread #{i} incremented");
})
})
.collect();
for t in threads {
t.join().unwrap();
}
println!("Result: {}", st_handler.state.read().val);
}Expectibly, this example won’t compile with Rc since it doesn’t implement Send.
Traits§
- UniRc
Lock - A common trait for
Rc<RefCell<T>>andArc<RwLock<T>>