4 releases
0.2.0 | Dec 16, 2023 |
---|---|
0.1.2 | Dec 16, 2023 |
0.1.1 | Dec 6, 2023 |
0.1.0 | Dec 4, 2023 |
#67 in #smart-pointers
9KB
112 lines
This crate provides a trait for universal reference-counted lock
with internal mutability
allowing multiple readers or a single writer,
which may represent either Rc<RefCell<T>>
or Arc<RwLock<T>>
.
lib.rs
:
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
.