2

How can I return a default value from an Option<&String>?

This is my sample/minimal code:

fn main() {
    let map = std::collections::HashMap::<String, String>::new();

    let result = map.get("").or_else(|| Some("")).unwrap(); // <== I tried lots of combinations
    
    println!("{}", result);
}

I know I could do something like this...

let value = match result {
    Some(v) => v,
    None => "",
};

... but I want to know if it is possible to implement it in a one-liner with or_else or unwrap_or_else? (It is important to make the default value lazy, so it does not get computed if it is not used)


These are some of the compiler suggestions I tried (I can put them all because SO won't allow me):

7 | let result = map.get("").or_else(|| Some("") ).unwrap();
  |                                          ^^ expected struct `String`, found `str`

.

7 | let result = map.get("").or_else(|| Some(&"".to_string()) ).unwrap();
  |                                     ^^^^^^--------------^
  |                                     |     |
  |                                     |     temporary value created here
  |                                     returns a value referencing data owned by the current function

.

7 | let result = map.get("").or_else(|| Some(String::new()) ).unwrap();
  |                                          ^^^^^^^^^^^^^
  |                                          |
  |                                          expected `&String`, found struct `String`
  | 

                                     help: consider borrowing here: `&String::new()`

.

7 | let result = map.get("").or_else(|| Some(&String::new()) ).unwrap();
  |                                     ^^^^^^-------------^
  |                                     |     |
  |                                     |     temporary value created here
  |                                     returns a value referencing data owned by the current function

. and also

6 |     let result = map.get("").unwrap_or_else(|| ""); // I tried lots
  |                                                ^^ expected struct `String`, found `str`
  |
  = note: expected reference `&String`
             found reference `&'static str`
francosang
  • 353
  • 3
  • 15
  • Please **always** post the full error message. Even if we can find it out ourselves, this can save us time. – Chayim Friedman May 28 '22 at 21:12
  • Does this answer your question? [Returning a default &str for HashMap<\_, String>](https://stackoverflow.com/questions/55887322/returning-a-default-str-for-hashmap-string) – Chayim Friedman May 28 '22 at 21:14

2 Answers2

3

If you really need a &String as the result, you may create a String for the default value with lifetime that's long enough.

fn main() {
    let map = std::collections::HashMap::<String, String>::new();

    let default_value = "default_value".to_string();
    let result = map.get("").unwrap_or(&default_value);
    
    println!("{}", result);
}

If the default value is a compile-time fixed value, the allocation of default_value can be avoided by using &str instead.

fn main() {
    let map = std::collections::HashMap::<String, String>::new();
    let result = map.get("")
        .map(String::as_str)
        .unwrap_or("default_value");

    println!("{}", result);
}
kotatsuyaki
  • 1,441
  • 3
  • 10
  • 17
  • 1
    Hi, thank you, it works! To be honest, I do not know if I need `&String`, it is the type returned by `map.get(...)` so I have it all around. It seems I will need to understand the differences between `String`, `&String`, `str`, `&str` and all the combinations. Thanks again! – francosang May 28 '22 at 11:28
  • 1
    `.as_deref()` is more concise than `.map(String::as_str)` in this case. – Colonel Thirty Two May 29 '22 at 02:18
0

How can I return a default value from an Option<&String>?

It's not trivial because as you've discovered ownership gets in the way, as you need an actual String to create an &String. The cheap and easy solution to that is to just have a static empty String really:

static DEFAULT: String = String::new();

fn main() {
    let map = std::collections::HashMap::<String, String>::new();

    let result = map.get("").unwrap_or(&DEFAULT); // <== I tried lots of combinations
    
    println!("{}", result);
}

String::new is const since 1.39.0, and does not allocate, so this works fine. If you want a non-empty string as default value it's not as good a solution though.

The cleaner and more regular alternative is to "downgrade" (or upgrade, depending on the POV) the &String to an &str:

let result = map.get("").map(String::as_str).unwrap_or("");

or

let result = map.get("").map(|s| &**s).unwrap_or("");

it's really not like you're losing anything here, as &String is not much more capable than &str (it does offer a few more thing e.g. String::capacity, but for the most part it exists on genericity grounds e.g. HashMap::<K, V>::get returns an &V, so if you store a String you get an &String makes sense even though it's not always quite the thing you want most).

Masklinn
  • 34,759
  • 3
  • 38
  • 57