Help with Pattern matching Options


#1

Hello everyone.

I recently started learning Rust and I am having a bit of trouble with pattern matching and options. Here is the scenario:

I have the following struct

pub struct Device {
    pub name: String,
    pub desc: Option<String>,
}

I am trying to access the description option using the following:

let desc = match d.desc {
    Some(desc) => desc,
    None => (), // I have tried multiple things here
}

I get:

error: match arms have incompatible types:
 expected `collections::string::String`,
    found `()` // or whatever else I put in there

What am I doing wrong here and what’s the good thing to do here? I am trying to loop over a list of devices and print their names and descriptions. In some cases description is None. What is the rust way to deal with that?

Thanks in advance.


#2

The problem is that depending on presence of desc, you are returning either desc which is string, or () - that’s what rust complain about. You choice is to either:

  1. Embrace the fact that there is no value in some cases and pass it higher. In that case there is no point of pattern matching here.

  2. In case of None set desc to empty string.


#3

Yeah the problem is that I have tried empty string / other string and it still complains:

expected `collections::string::String`,
    found `&'static str`

Does it need to be some kind of a reference or something?


#4

Use

"".to_string()

#5

Just came here to say I read this: http://hermanradtke.com/2015/05/03/string-vs-str-in-rust-functions.html
and used to_string().

But then I get this:

expected `()`,
    found `collections::string::String`

I am guessing this happens when desc is (). This makes it a bit impossible for a match to work, no?


#6

OK, this is how I “fixed” it.

println!("Description: {}", d.desc.unwrap_or("No desc".to_string()));

This looks rather strange. Is it the “Rust” way of doing it. I am also not sure why this works but the pattern match doesn’t.


#7

Study those to understand it:

https://doc.rust-lang.org/stable/book/strings.html
https://doc.rust-lang.org/stable/book/error-handling.html#unwrapping-explained


#8

Yes, using the various Option methods instead of pattern-matching is considered idiomatic. Although you should use to_owned() or into() instead of to_string().

There is no reason it shouldn’t. You haven’t shown your latest code with match, there must have been a typo somewhere.


#9
        match d.desc {
            Some(desc) => desc,
            None => "test".to_string(),
        }

Here it is. The only thing I changed was to add .to_string() (or to_ownder())

I’ll be reading over the link @Fiedzia provided so that I get a better understanding of what’s going on.


#10

What @WiSaGaN was trying to say

is that you should try this:

let _desc = match d.desc {
    Some(desc) => desc,
    None => "".to_string(),
};

Note the ; which ends the expression.

I would guess you did:

match d.desc {
    Some(desc) => desc,
    None => "".to_string(),
}

Note that you don’t associate what is returned from the match arms(no let before the match), so this would go to your function as a return type. You probably tried this in main or other function that returns -> () which would explain why you got that error. The function tried to return that String you told it to return but the function does not return -> String, it expects -> ().

Now, why None => (), doesn’t work? Because Some(desc) => desc returns a String and let desc can’t understand, should this desc be of type String or of type ()(let desc: String = or let desc: () =)?
Both arms should return the same type, so let desc can deduce the type that match returns.

Why None => "", doesn’t work? Because "" is a statically allocated string that would be placed in your data segment of the application and its type is &'static str, which means that it is a reference to a statically allocated string. At runtime it just references that string placed in your data segment.
String is a dynamically allocated string. Some(desc) => desc, returns a String, so you are left to make None => "", also return a String, so let desc = gets the same type returned from the match.
"".to_string() would return a String out of the statically allocated string.


#11

Related to this, I was wondering how to get a &str from an Option<String>, with a default? Unfortunately this does not work:

let desc = Some(String::from("test"));
let s = desc.as_ref().unwrap_or("default");

as there is no auto-Deref step involved that converts the &String to a &str. This would be possible:

let s = desc.as_ref().map(|v| &v[..]).unwrap_or("default");

but does someone have a nicer way?


#12

This is the correct way. Alternatively, you can use Deref::deref or |v| &**v or |v| v.as_ref() - all of these should convert &String to &str. When type ascriptions land, I think it will also be possible to write something like |v| v: &str.

I also find this quite unfortunate and in one of my libraries I even wrote an extension trait for Option<&T> which yields Option<&U> where U is whatever T derefs to. I’ve removed this bit some time after, though, because I finally discarded options altogether.


#13

Thank you for clearing that up @LilianMoraru!