Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a more friendly RefCell-like object?

Tags:

vector

rust

I'm looking for a class much like Vec<RefCell<T>>, in that it is the ultimate owner & allocator of all of its data, yet different pieces of the array can be be mutably borrowed by multiple parties indefinitely.

I emphasize indefinitely because of course pieces of Vec<T> can also be mutably borrowed by multiple parties, but doing so involves making a split which can only be resolved after the parties are done borrowing.

Vec<RefCell<T>> seems to be a world of danger and many ugly if statements checking borrow_state, which seems to be unstable. If you do something wrong, then kablammo! Panic! This is not what a lending library is like. In a lending library, if you ask for a book that isn't there, they tell you "Oh, it's checked out." Nobody dies in an explosion.

So I would like to write code something like this:

let mut a = LendingLibrary::new();
a.push(Foo{x:10});
a.push(Foo{x:11});
let b1 = a.get(0); // <-- b1 is an Option<RefMut<Foo>>
let b2 = a.get(1); // <-- b2 is an Option<RefMut<Foo>>

// the 0th element has already been borrowed, so...
let b3 = a.get(0); // <-- b3 is Option::None 

Does such a thing exist? Or is there another canonical way to get this kind of behavior? A kind of "friendly RefCell"?

If the answer happens to be yes, is there also a threadsafe variant?

like image 860
Jackson Loper Avatar asked Oct 18 '22 23:10

Jackson Loper


2 Answers

RefCell is not designed for long-lived borrows. The typical use case is that in a function, you'll borrow the RefCell (either mutably or immutably), work with the value, then release the borrow before returning. I'm curious to know how you're hoping to recover from a borrowed RefCell in a single-threaded context.

The thread-safe equivalent to RefCell is RwLock. It has try_read and try_write functions that do not block or panic if an incompatible lock is still acquired (on any thread, including the current thread). Contrarily to RefCell, it makes sense to just retry later if locking a RwLock fails, since another thread might just happen to have locked it at the same time.

If you end up always using write or try_write, and never read or try_read, then you should probably use the simpler Mutex instead.

like image 126
Francis Gagné Avatar answered Oct 27 '22 16:10

Francis Gagné


#![feature(borrow_state)]
use std::cell::{RefCell, RefMut, BorrowState};

struct LendingLibrary<T> {
    items: Vec<RefCell<T>>
}

impl<T> LendingLibrary<T> {
    fn new(items: Vec<T>) -> LendingLibrary<T> {
        LendingLibrary {
            items: items.into_iter().map(|e| RefCell::new(e)).collect()
        }
    }

    fn get(&self, item: usize) -> Option<RefMut<T>> {
        self.items.get(item)
            .and_then(|cell| match cell.borrow_state() {
                BorrowState::Unused => Some(cell.borrow_mut()),
                _ => None
            })
    }
}

fn main() {
    let lib = LendingLibrary::new(vec![1, 2, 3]);

    let a = lib.get(0); // Some
    let b = lib.get(1); // Some

    let a2 = lib.get(0); // None
}

This currently requires a nightly release to work.

like image 29
A.B. Avatar answered Oct 27 '22 16:10

A.B.