#smart-pointers #rc #lock #arc

uni_rc_lock

Provides a trait which may represent either Rc<RefCell<T>> or Arc<RwLock<T>>

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

MIT/Apache

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.

No runtime deps