Nicola Pedretti
Nicola Pedretti

Reputation: 5166

rust uwrap_or_else return a reference

I have a situation in a piece of dynamic programming where i either want to get the pre computed results, or call a function to compute these results.

This is the situation in short

let previous_results HashMap<String, Result> = HashMap::new();

for i in some_values {
   let result = previous_results.get(i).unwrap_or_else(|| calculate_results(i))
}

The rust compiler justly complains about the function call and it says

expected reference `&HashMap<std::string::String, Result>`
      found struct `HashMap<std::string::String, Result>`

This is because .get normally returns a reference to the object,and not the actual object, but the function returns an actual object. So i could just return a reference to what the function returns

let result = previous_results.get(i).unwrap_or_else(|| &calculate_results(i))

Note the &in front of the function call. But this is also an issue, cause a reference to something that is scoped within the anonymous function will be meaningless as soon as the anonymous function returns. And rust complains

cannot return reference to temporary value
returns a reference to data owned by the current function

What am i missing here? What is the correct approach to do this in rust?

Upvotes: 3

Views: 777

Answers (2)

Nicola Pedretti
Nicola Pedretti

Reputation: 5166

@rodrigo already answered my question with the correct approach, but I was a bit confused of what magic Cow was doing that solved my issue, so i want to clarify for others that might be confused as well.

The crux of the issue for my problem was that .get returns a reference to an element in the HashMap, but the function called by unwrap_or_else needs to return an owned value or it wont be valid once we are outside of the anonymous function scope.

So the solution that is a bit hidden in @rodrigo's answer is the map called after get, which can be used to turn whatever is returned by get into an owned value, so that unwrap_or_else can return an owned value, and the problem is solved.

Cow helps because cloning the value returned by .get is not the most efficient thing to do, unless you do need a copy later on. So Cow abstracts away the ownership and gets .get and unwrap_or_else to return the same type, without having to clone anything.

@rodrigo's answer should still be the accepted answer as it correctly solves the problem, but i wanted to provide a bit more context as i wasn't quite sure how the ownership was resolved.

Upvotes: 2

rodrigo
rodrigo

Reputation: 98486

You cannot return a reference to a local value (in your case the unwrap_or_else callback), because as that local value is dropped the reference is invalidated.

You could clone the value taken from the map, but that is usually not the most efficient way to go. And maybe the value isn't even cloneable.

So, that is one of the use cases for Cow:

use std::borrow::Cow;

for i in some_values {
   let result = previous_results
       .get(i)
       .map(Cow::Borrowed)
       .unwrap_or_else(|| Cow::Owned(calculate_results(i)));
}

And they you use the result more or less normally, as it implements Deref<Result>. But remember that the value inside may be borrowed from the map, so the Cow<'_, Result> value keeps the map borrowed.

Funnily, while the name Cow stands for Clone-On-Write, many times it is used when you need an Owned_Or_Borrowed.

The main drawback of Cow is that it requires the value to be ToOwned, that basically means to implement Clone, because of that C in the name. If your Result type is not Clone, I think that currently there is no solution in std but it is easy to implement your own, or use an available crate such as maybe-owned.

Upvotes: 5

Related Questions