Why is there a temporary created here?

use std::sync::Mutex;

pub struct First {
    amutex:Mutex<Second>,
}

pub struct Second {
    avec:Vec<u8>,
}

impl First {
    fn getRange(&self,start:usize,stop:usize) -> &[u8] {
        let refvec=&self.amutex.lock().unwrap();
        &(*refvec).avec.as_slice()[start..stop]
    }
}

fn main() {
    let f=First {
        amutex:Mutex::new(Second {
           avec:vec![0,2,4,6,8,10], 
        }),
    };
    let range=f.getRange(1,3);
    for dataitem in range {
        println!("{}",dataitem);
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0515]: cannot return value referencing temporary value
  --> src/main.rs:14:9
   |
13 |         let refvec=&self.amutex.lock().unwrap();
   |                     --------------------------- temporary value created here
14 |         &(*refvec).avec.as_slice()[start..stop]
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

For more information about this error, try `rustc --explain E0515`.
error: could not compile `playground` due to previous error

I was under the impression that I have created a reference. I assume the mutexguard is the temporary created but that does not need to survive for this code to work, according to my understanding.

:white_check_mark:

:x: :upside_down_face:

Indeed, you want shared, and in this instance, immutable, access to the Second('s Vec) instance.
But if the lock guard is released, then Rust cannot guarantee lack of mutation between the moment where it is released and the moment where you are looking at the start .. stop range of that vec, hence the error.


There are, in general, three kinds of solutions for your problem, here:

  • you can expose the Mutex (or at least a locking function) and have getRange be a method on Second (or the guard yielded by the locking function):

    impl Second { fn getRange(…) { … } }
    
    let locked_f = f.some_lock_function();
    let range = locked_f.getRange(1, 3);
    for dataitem in range { … }
    
  • You can offer a callback / scoped API, which allows you to have your own code run after the caller's, which means you can give access to the inner borrow to the caller:

    impl First {
        fn with_range<R> (
            &self,
            start: usize,
            stop: usize, 
            //             "return" value
            //                vvvvv
            with: impl FnOnce(&[u8]) -> R,
        ) -> R
        {
            let guard = self.amutex.lock().unwrap();
            with(&guard.avec[start .. stop])
        }
    }
    
    let f = First { … }
    f.with_range(1, 3, |range| {
        for dataitem in range {
            …
        }
    }); // <- lock released here
    

    This is an underrated pattern, imho.

  • And finally, you can, for a simple case such as this one, offer "sugar" for the previous callback-based API by offering your own lock guard:

    impl First {
        fn getRange(&self, start: usize, …) -> impl '_ + Deref<Target = [u8]> {
            AdhocDeref {
                guard: self.amutex.lock().unwrap(),
                deref_with: move |guard| &guard.avec[start .. stop],
                _phantom: <_>::default(),
            }
        }
    }
    

where adhoc_deref would be the Deref counterpart of scopeguard - Rust, which oddly enough, does not exist yet:

pub
struct AdhocDeref<
    Guard,
    Field : ?Sized,
    Deref : Fn(&Guard) -> &Field,
>
{
    pub guard: Guard,
    pub deref_with: Deref,
    pub _phantom: ::core::marker::PhantomData<fn(&Guard) -> &Field>,
}

impl<
    Guard,
    Field : ?Sized,
    Deref : Fn(&Guard) -> &Field,
>
    ::core::ops::Deref
for
    AdhocDeref<Guard, Field, Deref>
{
    type Target = Field;

    #[inline]
    fn deref (self: &'_ Self)
      -> &'_ Field
    {
        (self.deref_with)(&self.guard)
    }
}
4 Likes

Thanks for this very informative reply. The second recommendation was a perfect idea for my problem. I went with that.

1 Like

In general, the very point of "guard" objects is that they are the token with which you will be able to do useful things to the guarded object. It wouldn't make sense for a Mutex to hand out guards if they didn't actually enforce the invariant of the mutex (i.e. that it is held while someone is poking around in the locked object).

1 Like

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.