Help improving ergonomics for returning borrows from temporaries

Hi Everyone,

At the suggestion of @Ixrec in another thread, I'm posting this question is to address what I consider to be my only major gripe about Rust - Situations where error[E0515] : cannot return value referencing temporary value errors cause me to create unnatural interfaces between functions. Consider this code:

use std::cell::{RefCell, Ref};

pub struct Container {
    data : RefCell<Vec<u8>>,
}

impl Container {
   pub fn get_data(&self) -> &Vec<u8> {
      &*self.data.borrow()
   }
}

fn main() {
   let container = Container{data : RefCell::new(vec![1u8; 100])};

   let chars = container.get_data();
    
   println!("{}", chars[20]);
}

Obviously it doesn't work because the Ref<> can't be dropped inside the function without breaking the ability of RefCell<> to track its borrows.

So, if only there was a way to send the Ref<> outside the function along with the return value, so it could be dropped after the return value is dropped. Consider this kludge.

pub struct TrashBag<T> {
   trash : Option<T>
}

impl <T>TrashBag<T> {
   pub fn new() -> TrashBag<T> {
      TrashBag{trash : None}
   }
   pub fn done(&mut self, input : T) -> &T {
      self.trash = Some(input);
      self.trash.as_ref().unwrap()
   }
}

impl Container {
   pub fn get_data<'b, 'a : 'b>(&'a self, trash_bag : &'b mut TrashBag<Ref<'a, Vec<u8>>>) -> &'b Vec<u8> {
      trash_bag.done(self.data.borrow())
   }
}

fn main() {
   let container = Container{data : RefCell::new(vec![1u8; 100])};

   let mut trash_bag = TrashBag::new();
   let chars = container.get_data(&mut trash_bag);
    
   println!("{}", chars[20]);
}

Works now. It's really ugly, but I don't think it's unsafe or unsound.

You could certainly point out that it's not any better than just returning the Ref from the function and letting the caller deal with it, and you would have a point. But that's not the reason I'm posting.

In a perfect world, it would be nice not to leak any implementation details about Container (e.g. the fact that it uses a RefCell internally) to the caller. So I want to make the fn prototype of the get_data function as close to the original version as possible.

So my first question is: is there anything already available within Rust to make this cleaner and more ergonomic? For example, is there a way I could allocate the TrashBag on the heap from within get_data(), and then tie its lifetime to the return value's? It doesn't feel like that is currently possible because the owner of the TrashBag would need to be moved outside the function, and that would invalidate the return value's reference.

Which brings me to my second question: Does this pattern, or something along these lines, make sense as a language feature? For example, the compiler already turns "x = &5;" into "_x = 5; x = &_x;" I know what I am talking about is more involved, but I believe the compiler knows everything it needs to know in order to implement this pattern (for statically linked code anyway), or something similar, and I think it would greatly improve the ability to create clean abstractions.

Anyway, any thoughts are appreciated.

If your only goal is to hide implementation details, then return an impl Deref<Target = T>:

use std::cell::RefCell;
use std::ops::{Deref, DerefMut};

pub struct Container {
    data : RefCell<Vec<u8>>,
}

impl Container {
    pub fn get_data<'a>(&'a self) -> impl Deref<Target = Vec<u8>> + 'a {
        self.data.borrow()
    }
    pub fn data_mut<'a>(&'a self) -> impl DerefMut + Deref<Target = Vec<u8>> + 'a {
        self.data.borrow_mut()
    }
}

fn main() {
   let container = Container{data : RefCell::new(vec![1u8; 100])};

   let chars = container.get_data();
    
   println!("{}", chars[20]);
}

Playground
Note, however that returning an impl Trait item is not possible to do within traits.


You cannot write unsafe or unsound code using only safe code (which is backed by sound code as well). That's a major point of Rust.

4 Likes

Thanks! That's a huge improvement over TrashBag<T> :wink:

1 Like

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