以下是我可以使用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);
}
虽然以下代码
fn main() {
let r = String::from("a");
let a = Some(&r);
let b = a.unwrap_or("");
println!("{}", b);
}
失败并出现错误:
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`
据我所知,编译器在确定类型时的推理如下:
match
的情况下,它确定b
是&str
,因为None => ""
是&str
而Some(name) => name
是&String
所以它可以成为&str
。unwrap_or
的论证是&str
的情况下,它没有输入b
作为&str
,而是看到a
(即&String
)中持有的类型与unwrap_or
的参数类型之间存在差异。这两种情况之间有什么区别使得类型推导以这种方式工作?
unwrap_or
是否被实现为接受与选项包含的类型完全相同的类型,而不是仅接受它放在匹配中的通用值?
此外,有没有办法让unwrap_or
在这种情况下工作,而不必在外部范围声明String
或更改选项包装的类型?
我得到一个让其工作的答案是:
let b = a.map(|s| s.as_ref()).unwrap_or("")
与match
相同的效果,比match
更短,更明确地说明了类型发生了什么。
松散地说,你是正确的一半。
a
是一个Option<&String>
。 &String
可以被强制进入&str
,但&str
不能被强制进入&String
。看到这段代码:
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);
}
如果foo(b);
没有注释,它会告诉你它预期String
,得到str
。
match
语句返回&str
(从&String
转换),但unwrap_or
期望与你正在调用的Option
相同的类型,&String
,&str
不能没有手动更改(String::from
,to_string
,to_owned
等)
现在,我将尝试简要解释为什么会出现这种情况,但是因为我不是这个领域的专家,所以要考虑一下。
当使用字符串文字("foo"
)时,程序在编译时会创建一些内存段,以二进制形式表示此文本。这就是为什么字符串文字是&str
而不仅仅是str
:它不是原始文本,而是它是对启动时已经存在的文本的引用。这意味着字符串文字存在于无法修改的特定内存部分中,因为它们与其他对象("fooquxegg"
)并列 - 并且尝试将字符添加到字符串文字中会导致您覆盖下一个对象线,非常糟糕。
然而,String
是可修改的。因为我们无法确定String中有多少个字符,所以它必须存在于“堆”中,如果某些东西试图“成长为”其他东西,则对象可以被移动,而String
类型基本上用作指针在堆中的那个文本,很像Box<T>
指向堆中的T
。
由于这个原因,虽然&String
可以成为一个&str
(它指向一定数量的文本,在这种情况下,在堆中的某个地方而不是静态内存),&str
不能保证成为&String
。如果你将一个字符串文字(比如我们的"foo"
)转换成一个可能被修改的&String
,然后尝试将一些字符附加到它(比如"bar"
),会覆盖别的东西。
话虽如此,看看这段代码:
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);
}
这个代码实际上是你的unwrap_or
例子的反面:一个Option<&str>
,用&String
作为默认打开。这将编译,因为&String => &str
是一个有效的转换,编译器将自动插入必要的转换而不需要它是显式的。所以不,unwrap_or
没有实现接受完全相同的对象类型;任何可以胁迫T
的对象(在这种情况下,T
是&str
)是有效的。
换句话说,你的unwrap_or
例子失败了因为&str
不能成为&String
,而不是因为unwrap_or
方法异常挑剔。不,没有使用unwrap_or
可能没有办法让String
。你最好的选择是在字符串文字上使用to_string
,如下:
fn main() {
let s = String::from("Hello, world!");
let o = Some(s);
let r = o.unwrap_or("Goodbye, world!".to_string());
println!("{}", r);
}
因为map_or
虽然看似很有希望,但不会简单地允许你为地图输入|s| &s
(在关闭结束时删除引用)。