Loosely speaking, you're half correct.
a
is an Option<&String>
. An &String
can be coerced into an &str
, but an &str
cannot be coerced into an &String
. See this code:
fn main() {
let a = String::from("This is a String");
let a_r = &a;
foo(a_r);
bar(a_r);
let b = "This is an &str";
// Next line will cause a compilation error if uncommented
// foo(b);
bar(b);
}
fn foo(s: &String) {
println!("Foo: {}", s);
}
fn bar(s: &str) {
println!("Bar: {}", s);
}
Playground link
If foo(b);
is uncommented, it'll tell you that it expected String
, got str
.
The match
statement returns an &str
(converted from an &String
), but unwrap_or
expects the same type as the Option
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 a str
: 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 the String
type basically serves as a pointer to that text in the heap, much like a Box<T>
points to a T
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:
fn main() {
let s = "this is an &str";
let b = String::from("this is a String");
let s_o : Option<&str> = Some(s);
let s_or_def = s_o.unwrap_or(&b);
println!("{}", s_or_def);
}
Playground link
This code is effectively the inverse of your unwrap_or
example: An Option<&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 to T
(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 the unwrap_or
method is unusually picky. No, there is likely no way to unwrap_or
without using a String
. Your best bet is to simply use to_string
on a string literal, as per:
fn main() {
let s = String::from("Hello, world!");
let o = Some(s);
let r = o.unwrap_or("Goodbye, world!".to_string());
println!("{}", r);
}
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).