Add owned guard types to standard library

tokio provides some owned guard types, like:

async-lock also has something similar:

The common pattern is that they are all created from Arc of some type. I found this pattern very useful.

Does it make sense to add this feature to some of the standard library types? For example:

  • Add to Rc<RefCell<T>>:
    • borrow_owned,
    • try_borrow_owned,
    • borrow_mut_owned,
    • try_borrow_mut_owned.
  • Add to Arc<Mutex<T>>:
    • lock_owned,
    • try_lock_owned.
  • Add to Arc<RwLock<T>>:
    • read_owned,
    • try_read_owned,
    • write_owned,
    • try_write_owned.
1 Like

I think it would be useful to have these in std because it's difficult to guarantee for outside crates they're upholding all the necessary invariants.

1 Like

There is an open feature to add this to parking-lot.

What's the motivation/use-case for adding those; what problem does it solve?

2 Likes

Though, there is a crate that claims to

1 Like

The owned guard has 'static lifetime. You can store them into data structures or return them from a function without the lifetime constraint that normal borrowing guards have.

For example, here is a function that traverses a binary tree iteratively:

use std::cell::RefCell;
use std::rc::Rc;

// Using `Box` here may be better for real codes, but it is outside the scope.
// For now, just assume this is provided by some third party, and I can’t
// change its definition.
pub struct TreeNode {
    pub val: i32,
    pub left: Option<Rc<RefCell<Self>>>,
    pub right: Option<Rc<RefCell<Self>>>,
}

pub fn inorder_traversal(mut root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
    let mut result = Vec::new();
    let mut stack = Vec::new();

    loop {
        if let Some(node) = root {
            root = node.borrow().left.clone(); // <- Borrow.
            stack.push(node);
        } else if let Some(node) = stack.pop() {
            let node_ref = node.borrow(); // <- Borrow.

            result.push(node_ref.val);

            root = node_ref.right.clone();
        } else {
            break;
        }
    }

    result
}

Note that I called borrow before pushing a node into a stack, and borrowed again after popping it out of the stack. If I can have an owned guard, I can just push the guard into the stack, then after popping the guard out of the stack, I don’t have to borrow it again.

Another possible usage is that I can implement thread notification using owned mutex guard:

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let mutex = Arc::new(Mutex::new(()));
    let guard = Arc::clone(&mutex).lock_owned().unwrap(); // Using imaginary `lock_owned` method.

    let sender = thread::spawn(move || {
        drop(guard); // Unlock the mutex to notify the receiver thread.
    });

    let receiver = thread::spawn(move || {
        mutex.lock().unwrap(); // Wait for the signal from the sender thread.
    });

    sender.join().unwrap();
    receiver.join().unwrap();
}

(I know this can be done using channels, but someone may consider channels being too heavy for simple cases.)

3 Likes

The first example is a good example, the second one won't ever work, because MutexGuard is !Send and an owned version won't change that.

1 Like

Fair enough, but it could still be done with parking_lot, since its mutex guard can be Send, although through a feature switch. I think @bjorn3 is referring to Amanieu/parking_lot#273.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.