#Returning a reference to data owned by a function

22 messages · Page 1 of 1 (latest)

pulsar patrol
#

See the following Rust Playground link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=272b2760cc6a2796cf913ee584fc4205

The above is a contrived example of something I'm running into. I'm trying to implement an external trait for which one of its function signatures takes in a reference and then returns a Future<...> (which necessarily holds a reference to data that can generate the result of the Future, I think).

The playground version is just trying to mutate a string and return a reference to the mutated string, which is a similar situation. In the example, fn mutate_string(...) is a trait function whose signature I can't modify.

My naive attempt at solving the problem is the provided mutate_string_internal() function, which mutates then returns an owned data type. The problem is, I can't then return a reference to the owned String from the parent function, because the String is owned by the internal function and goes out of scope when the function ends.

mutate_string_trivial() has the correct function signature and works, but I can't figure out how to actually mutate the string, because I have no mutable arguments, I'd need to create data owned by the function to cause a mutation, and then I can't return a reference to it because it goes out of scope.

The only way I can think of to solve this off the top of my head would be to essentially implement a manual garbage collector, where I create a lazy_static variable to own the data with a reference counter. I can then return a reference to the data owned by the lazy_static global variable.

Then I'd need to periodically check and if the only reference left is the lazy_static's, it "garbage collects" and deletes the owning variable.

Are there any reasonable patterns to solve this conundrum? Am I misunderstanding something or missing something obvious? Or am I approaching this from the wrong direction somehow?

stray mica
#

The only way to mutate something behind a shared ref is with UnsafeCell, which can be done safely with RefCell, Mutex, or RwLock. If you can't do that, you can leak the value to make it 'static, which will fulfill the lifetime requirement.

fn mutate_string<'a: 'b, 'b>(s: &'a str) -> &'b str {
    let s = s.to_owned() + ":mutated";
    let s = s.into_boxed_str();
    Box::leak(s)
}
pulsar patrol
#

if I leak the value, will it live forever?
e.g., if I repeatedly call the trait function, will I leak memory until my program crashes? (my intuition says yes)

stray mica
#

You can soundly return it to a box and thus drop it if you can guarantee it won't be accessed again.

pulsar patrol
#

hm... the problem is that the trait function is part of a framework, and the reference kind of goes into the ether of the framework where I don't have control of the code without forking the repository

I know that it will almost certainly stop being used after a few seconds, but that still ends up looking very similar to my above approach of holding a reference to it and periodically checking if it's okay to drop the reference

stray mica
#

What about RefCell?

pulsar patrol
#

let me read the docs

stray mica
#

That would look something like this:

use std::cell::RefCell;
fn main() {
    let s = RefCell::new("blork".to_string());
    let result = mutate_string(&s);
    println!("{}", result.borrow());
}

fn mutate_string<'a: 'b, 'b>(s: &'a RefCell<String>) -> &'b RefCell<String> {
    s.borrow_mut().push_str(":mutated");
    s
}
pulsar patrol
#

my understanding is that it's not workable because that modifies mutate_string()'s function signature, which is a trait function not part of my code, so I can't modify the signature

I think this falls into the same trap, I can make a RefCell<String> or even a RwLock<String> or something, but if I try to do return &cell.borrow() , because cell was created in the internal function, it runs into the same issue

stray mica
#

What's the signature of the actual function?

pulsar patrol
#

fn load_path<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<Vec<u8>, AssetIoError>>

#

it takes a path and returns a future whose result is the binary data of the file residing at that path

#

I want to change the path it tries to load and load an alternate path instead

#

then return a future to that path's data

#

I'm able to solve it for the Future by using async move {} to change the owner of the function execution scope to the Future

#

but I'm having trouble doing something similar for a similar case with signature

        &self,
        path: &Path,
    ) -> Result<Box<dyn Iterator<Item = PathBuf>>, AssetIoError>```
stray mica
#

Like this?

use std::path::Path;
use std::future::Future;
type BoxedFuture<'a, F> = Box<dyn Future<Output = F> + 'a>;

pub fn load_path<'a>(path: &'a Path) -> BoxedFuture<'a, Vec<u8>> {
    Box::new(async {
        tokio::fs::read(path.join("another")).await.unwrap()
    })
}
pulsar patrol
#

yeah, very similar although I'm not using tokio

stray mica
#

Just wanted something to await

#

Your other one doesn't have a lifetime so you have to clone stuff before moving:

pub fn read_directory(path: &Path) -> Result<Box<dyn Iterator<Item = PathBuf>>, ()> {
    let path = path.to_path_buf();
    let iter = ["a", "b"].into_iter().map(move |n| path.join(n));
    Ok(Box::new(iter))
}
pulsar patrol
#

oh, wow, for that one I missed the difference between .iter() and .into_iter()

#

thank you, this was super helpful, both cases now compile without weird reference counting/garbage collection