Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I return a new struct from Option::unwrap_or?

I have a HashMap that uses a char for the key and a struct for the value.

The HashMap's get() method will often be called with keys that are not in the HashMap, so I'd like to use unwrap_or() on the returned Option to create a default struct value. However, when I attempt to do so, the compiler throws the following error (with temp being the default value that I'm attempting to return):

lib.rs:51:4: 51:8 error: `temp` does not live long enough

Here is a small reproducer:

struct Sample {
    thing: i32
}

fn do_stuff() {
    let map = HashMap::<char, Sample>::new();

    let sample = map.get(&'a').unwrap_or({
        let temp = Sample {
            thing : 0
        };
        &temp
    });
}

I have two questions:

  1. Is there a way to make the temp binding live longer?
  2. Is there a better way to do fall back to a default struct when using an Option?
like image 229
Ian Andrews Avatar asked Sep 21 '15 00:09

Ian Andrews


2 Answers

For your precise scenario, you can do something like this:

use std::collections::HashMap;
struct Sample {
    thing : i32
}

fn main() {
    let map = HashMap::<char, Sample>::new();
    let temp = Sample { thing : 0 };
    let sample = map.get(&'a').unwrap_or(&temp);
}

Often, though, you want something more like this:

use std::collections::HashMap;
#[derive(Clone)]
struct Sample {
    thing : i32
}

fn get_sample(map: &HashMap<char, Sample>) -> Sample
{
    map.get(&'a').cloned().unwrap_or_else(|| {
        Sample { thing : 0 }
    })
}

If you really want to conditionally initialize and take the address of a local variable, it's possible, but can't be written using unwrap_or or unwrap_or_else:

use std::collections::HashMap;
struct Sample {
    thing : i32
}

fn main() {
    let map = HashMap::<char, Sample>::new();
    let temp;
    let sample = match map.get(&'a') {
        Some(sample) => sample,
        None => {
            temp = Sample { thing : 0 };
            &temp
        }
    };
}
like image 193
Eli Friedman Avatar answered Oct 22 '22 20:10

Eli Friedman


Note that your question shows you have a root misunderstanding:

How do I return a new struct

But then your code says this:

&temp

That's a reference to a struct. The problem is that your struct only lives for the duration of the block passed as an argument to unwrap_or. As soon as the block is over, any variables that aren't returned from the block are dropped, invalidating any references to them.

Is there a way to make the temp binding live longer?

There is one way to make a binding live longer: move where it is in code. If you move the creation of a variable earlier, its lifetime starts earlier. That's why Eli Friedman's first solution works.

If you change your code to return a struct instead of a reference, then that explains how the second solution works, and better matches how you are modeling the problem. You can't always go from a reference to a non-reference cheaply though, and sometimes it's not even possible.

like image 30
Shepmaster Avatar answered Oct 22 '22 20:10

Shepmaster