Ideas to improve ergonomics of smart-pointer that depends on intermediates

Hello,

I have a need for a smart-pointer that may point to mutable or immutable data, and can be checked at runtime. Here is a simplified version: Rust Playground

But the need for intermediate local variables with the same lifetime means these pointers are a bit ugly to initialize. For example, I currently need to do this (from the playground):

    let mut write_guard = match access_mut {
        true => Some(data.write().unwrap()),
        false => None
    };
    let read_guard = match access_mut {
        true => None,
        false => Some(data.read().unwrap())
    };
    let my_ref = match access_mut {
        true => MyRef::Mut(write_guard.as_mut().unwrap().get_mut(key).unwrap()),
        false => MyRef::Const(read_guard.as_ref().unwrap().get(key).unwrap())
    };

Does anybody have ideas to make this a little friendlier / more readable / better modularized?

NOTE: I'm open to changing what's stored in the smart pointer enum, but I don't want to put the HashMap lookup inside the deref operation because of the performance implications.

Any ideas are appreciated.

Thank you.

    let mut write_guard;
    let read_guard;
    let my_ref = if access_mut {
        write_guard = data.write().unwrap();
        MyRef::Mut(write_guard.get_mut(key).unwrap())
    } else {
        read_guard = data.read().unwrap();
        MyRef::Const(read_guard.get(key).unwrap())
    };
    
    //BONUS FEATURE: can't access the guards here:
    // error[E0381]: used binding `read_guard` is possibly-uninitialized
    //println!("{:?}", read_guard);

    //Do a lot of stuff with my_ref, taking care to only access the mutable variant if it exists
    println!("{}", *my_ref);
5 Likes

That's definitely an improvement. Thanks.

I am hoping for ideas that are a little more drastic - for example, a slightly more encapsulated type that could be created like:

let (_intermediates, my_ref) = MyRef::from_borrow(true, "some_key", &data_store);

The old owning_ref crate had a nice interface but I read (Here: Why the “Null” Lifetime Does Not Exist - Sabrina Jewson) that it's been proven unsound. I think ouroboros is cool, but I thought there might be a way to do something a little lighter-weight / more straightforward if we have the ability to stash the intermediates on the stack.

I know I could reach for a macro, but I wanted to see if there is a way to do it without one.

Thanks again for your thoughts.

And what should this do? What should the type of _intermediates be? What's the boolean?

And what should this do? What should the type of _intermediates be? What's the boolean?

This was with regard to the types here: Rust Playground

impl<'a> MyRef<'a, T> {
    fn from_borrow(access_mut: bool, key: &str, data: &RwLock<HashMap<String, String>>) -> (???, Self)
}

So, if I could implement this method in an abstract machine language, I'd create the intermediates in their final location (likely the stack of the caller), and then borrow those intermediates to use when initializing Self. I know Rust doesn't have the syntax to allow precisely that. But is there a creative way to accomplish the same thing?

Thanks.

Do you mean like this?

Generally, you won't be able to get rid of the muts if you want mutable access dynamically. Rust very much does want to know mutability at compile time, so ultmately, the leaf guard granting the actual mutability access has to be mutable. Or you can hide it behind an interior mutability API taking a callback or something.

Do you mean like this ?

The mut annotations are fine.

The bigger thing is that the MyGuard type is basically the "intermediates" I was talking about, but then the my_ref smart pointer needs to be initialized from those intermediates. (doing a HashMap lookup in my example, but in reality it might be a more expensive tree traversal) So creating a MyRef requires two function calls in the caller.

I was trying to explore whether there might be a workaround for the constraints that add up to "no-practical-self-referencing" by using two structures that are created sequentially in one operation.

The problem with that is that you aren't going to be able to do that with functions. Values are moved when returned from a function, so there's no way to return a reference along with its referent, even if no self-reference is involved.

You can write a macro, though.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.