Reputation: 5166
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
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
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