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
match
it determinesb
is a&str
, sinceNone => ""
is a&str
andSome(name) => name
is a&String
so it can become a&str
.In the case where the argument to
unwrap_or
is a&str
, instead of typingb
as 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.
a
is anOption<&String>
. An&String
can be coerced into an&str
, but an&str
cannot be coerced into an&String
. See this code:Playground link
If
foo(b);
is uncommented, it'll tell you that it expectedString
, gotstr
.The
match
statement returns an&str
(converted from an&String
), butunwrap_or
expects the same type as theOption
that you're calling, an&String
, which an&str
cannot 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&str
and 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 theString
type basically serves as a pointer to that text in the heap, much like aBox<T>
points to aT
in the heap.For this reason, while an
&String
can 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&str
cannot 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&String
which 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_or
example: AnOption<&str>
which is being unwrapped with an&String
as a default. This will compile, because&String => &str
is a valid conversion, and the compiler will automatically insert the necessary conversion without needing it to be explicit. So no,unwrap_or
is not implemented to accept the exact same object type; any object which can coerce toT
(In this case,T
is&str
) is valid.In other words, your
unwrap_or
example fails because an&str
cannot become an&String
, rather than because theunwrap_or
method is unusually picky. No, there is likely no way tounwrap_or
without using aString
. Your best bet is to simply useto_string
on a string literal, as per:because
map_or
, while seeming promising, will not simply allow you to input|s| &s
for a map (reference is dropped at the end of the closure).