Guard dropped while borrowing from it

Hi!
I have a read guard:

pub struct Ref<'a, T> {
    inner: &'a T,
    borrow: SharedBorrow<'a>,
}

SharedBorrow is a reference to the lock and implements Drop.
I'm trying to change Ref's definition to:

pub struct Ref<'a, T> {
    inner: T,
    borrow: SharedBorrow<'a>,
}

This way I could map it to something like RwLockReadGuard for example.

The issue is the compiler is too cooperative. I'm able to deref the guard, keep the reference and drop the guard.
If I switch back to &T the borrow checker stop me and tell me I can't move out of a borrowed value.
I'm using a "basic Deref implementation", I also tried with a method.

impl<T> core::ops::Deref for Ref<'_, T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.inner
    }
}
impl<'a, T> Ref<'a, T> {
    pub fn inner(&self) -> &'_ T {
        &self.inner
    }
}

I'd like to know why this is happening and if there is a way to have the expected behavior. Thanks

I don’t understand what you mean by map here.

Is this referring to the inner: T version? If yes, I don’t really understand what’s going on here and you should provide the details of what Deref implementation you used.

It is impossible to me to determine what the expected behavior that you’re trying to achieve is. You only offer small cues to what behavior you don’t expect and say nothing about what you do expect. Also if you want to have an explanation about why some a specific code does or doesn’t compile you must provide the code itself (optimally as a small/reduced example) or at least a way more detailed description of what you’ve done, including the relevant sections of your actual code. And in the case of something not compiling, you should include the error message, too.

1 Like

By map I mean something like std::cell::map that would let me change the type in the Ref.

Yes and I did provide the Deref implementation, it's the same for both versions.

Here's an example that shows the problem:

// this data-structure contains a struct `AllStorages`
let world = World::new();
// with this method I lock what is essentially a `RwLock` and get back a read guard `Ref`
let guard: Ref<AllStorages> = world.all_storages().unwrap();
// then I `deref` it
let all_storages: &AllStorages = &*guard;
// drop the guard
drop(guard);
// and use the reference
all_storages.clone();

With inner: &'a T, I get a compile error:

error[E0505]: cannot move out of `guard` because it is borrowed
  --> src\main.rs:52:10
   |
50 |     let all_storages: &AllStorages = &*guard;
   |                                        ----- borrow of `guard` occurs here
51 | 
52 |     drop(guard);
   |          ^^^^^ move out of `guard` occurs here
53 | 
54 |     all_storages.clone();
   |     ------------ borrow later used here

With inner: T, guard has the type Ref<&AllStorages> and I don't get any compile error, this only thing I get is a warning because I don't use the clone:

warning: unused return value of `std::clone::Clone::clone` that must be used
  --> src\main.rs:54:5
   |
54 |     all_storages.clone();
   |     ^^^^^^^^^^^^^^^^^^^^^
   |
   = note: `#[warn(unused_must_use)]` on by default
   = note: cloning is often expensive and is not expected to have side effects

I would expect both versions to have the same behavior.

Ah, okay that was the detail that was missing. Your Deref implementation provides access to the T inside Ref<'a, T> (by reference). The signature is fn deref(self: &Ref<'a, T>) -> &T or more explicitly fn deref<'b>(self: &'b Ref<'a, T>) -> &'b T. This means that for &AllStorages it is

fn deref<'b, 'c>(self: &'b Ref<'a, &'c AllStorages>) -> &'b &'c AllStorages

Since shared references can be copied, it is possible to copy the &'c AllStorages out of the &'b &'c AllStorages by dereferencing, which is what is happening implicitly in the assignment of all_storages: &AllStorages. The lifetime 'c here is not bound by anything, so it can happily live on while the Ref<'a, &'c AllStorages> gets dropped. In fact, it must also have already lived before the Ref was initially constructed.


By the way, I still haven’t gotten any explanation as to what this Ref type is actually for and what behavior and API you want it to have. If there’s unsafe code involved, keep in mind that it’s easy to get things wrong and produce unsound APIs even for people more experienced with Rust’s ownership and borrowing system. And that there are often existing safe alternatives out there.

Ok ok that makes sense thank you. I guess there is no way for me to tie the lifetime.

Ref is a read guard for an AtomicRefCell, a RwLock were you can only use try_read and try_write so there is no waiting. If the AtomicRefCell is already borrowed you get an error.

The addition I make is to have Send and Sync checked at runtime. And since all RwLock that I know require T: Send + Sync to be Send + Sync, I can't use a safe alternative. Additionally I can't find any read guard with a inner: T, I understand why now.

You can find the inner: &'a T version here. Don't mind taken and the Drop implementation, I removed them on my local version.

FWIW, you may find the following crate (of mine, #shamelessplug) useful:

Interesting, it would solve the problem. I suppose simple callback too.
The problem is this is exposed to users and they would have to use callbacks or sugar all the way.

1 Like

I see. This API and the unsaftety do seem to make sense.

I haven’t thought this through very deeply but perhaps simply letting try_borrow() return an Ref<'a, &'a S> (with inner: T for Ref<'a, T>) solves the problem.

With inner: T, it's with this definition that the problem occurs.

fn try_borrow<'a>(&'a self) -> Result<Ref<'a, &'a T>, error::Borrow> {

try_borrow seems to give the correct lifetime. I tried to borrow for 'static and the compiler said I couldn't borrow for longer than 'a which make sense.
But I suppose the Deref implementation just doesn't have access to this information as you said.

If the inner type of Ref is always going to be some flavor of pointer, you could do something like this:

impl<T:Deref> core::ops::Deref for Ref<'_, T> {
    type Target = T::Target;

    fn deref(&self) -> &T::Target {
        self.inner.deref()
    }
}

Cool! The types might not implement Deref but maybe I can use it :thinking: Thank you

Oh, of course! As I said, I didn't think this through too much.

The moment you want to write fn …(&'a self, …) -> … + 'a to satisfy the borrow checker, what you really want are higher ranked trait bounds.

1 Like