The following is a valid file I can compile with Rust 1.23.0:
fn main() {
let r = String::from("a");
let a = Some(&r);
let b = match a {
Some(name) => name,
None => "",
};
println!("{}", b);
}
Whilst the following code
fn main() {
let r = String::from("a");
let a = Some(&r);
let b = a.unwrap_or("");
println!("{}", b);
}
Fails with the error:
error[E0308]: mismatched types
--> src/main.rs:4:25
|
4 | let b = a.unwrap_or("");
| ^^ expected struct `std::string::String`, found str
|
= note: expected type `&std::string::String`
found type `&'static str`
As far as I can see, the compilers reasoning when determining types here is as follows:
In the case with the
matchit determinesbis a&str, sinceNone => ""is a&strandSome(name) => nameis a&Stringso it can become a&str.In the case where the argument to
unwrap_oris a&str, instead of typingbas a&str, it sees there's a difference between the type held ina(namely&String) and the type of the parameter tounwrap_or.
What is the difference between these two cases that makes type deduction work this way?
Is unwrap_or implemented to accept the exact same type as the one wrapped by the option instead of just accepting a generic value it place inside a match ?
Furthermore, is there any way to make the unwrap_or work in this case, without having to declare a String in the external scope or change the type wrapped by the option ?
One answer I got somewhere else for making them work is this:
let b = a.map(|s| s.as_ref()).unwrap_or("")
Same effect as the match, somewhat shorter, more explicit than the match about what's happening to the types.
Loosely speaking, you're half correct.
ais anOption<&String>. An&Stringcan be coerced into an&str, but an&strcannot be coerced into an&String. See this code:Playground link
If
foo(b);is uncommented, it'll tell you that it expectedString, gotstr.The
matchstatement returns an&str(converted from an&String), butunwrap_orexpects the same type as theOptionthat you're calling, an&String, which an&strcannot become without a manual change (String::from,to_string,to_owned, etc)Now, I'm going to try to give a brief explanation for why this is, but take it with a grain of salt because I am not an expert in this field.
When using a string literal (
"foo"), the program, on compilation, creates some segment of memory which represents this text in binary form. This is why a string literal is an&strand not just astr: It isn't raw text, but instead, it's a reference to text that already exists at startup. This means that string literals live in a specific portion of memory which cannot be modified, because they're wedged up beside some other object - ("fooquxegg") - and attempting to add characters to a string literal would cause you to overwrite the next object in line, which is Very Bad.A
String, however, is modifiable. Since we can't determine how many characters are in a String, it has to live in the "heap", where objects can be shifted around if something attempts to "grow into" something else, and theStringtype basically serves as a pointer to that text in the heap, much like aBox<T>points to aTin the heap.For this reason, while an
&Stringcan become an&str(it's pointing to a certain amount of text somewhere, in this case, somewhere in the heap rather than the static memory), an&strcannot be guaranteed to become an&String. If you converted an string literal (like our"foo"), which lives in a portion of memory that can't be modified, into an&Stringwhich could potentially be modified, and then attempted to append some characters to it (say"bar"), it would overwrite something else.With that being said, look at this code:
Playground link
This code is effectively the inverse of your
unwrap_orexample: AnOption<&str>which is being unwrapped with an&Stringas a default. This will compile, because&String => &stris a valid conversion, and the compiler will automatically insert the necessary conversion without needing it to be explicit. So no,unwrap_oris not implemented to accept the exact same object type; any object which can coerce toT(In this case,Tis&str) is valid.In other words, your
unwrap_orexample fails because an&strcannot become an&String, rather than because theunwrap_ormethod is unusually picky. No, there is likely no way tounwrap_orwithout using aString. Your best bet is to simply useto_stringon a string literal, as per:because
map_or, while seeming promising, will not simply allow you to input|s| &sfor a map (reference is dropped at the end of the closure).