您的期望是合理的。如果编译器有魔法可以将
&String
转换为
&str
,那么它应该也能将
Option<&String>
转换为
Option<&str>
或者(同样地)将
AnyType<&String>
转换为
AnyType<&str>
。
但是,在这种情况下,编译器的魔力非常有限。矛盾的是,强制转换是从试图
减少编译器中的魔力而出现的
(*)。要理解这一点,您需要了解强制转换和重新借用之间的联系,并跟随我进行一次小小的 Rust 历史探究。
前段时间,在Rust中你经常会看到这样的结构:
&*x
:一个“reborrow”。对于像
Box
这样的指针类型,你希望能够用
*
来解引用它们以获取内部的值。如果你需要一个
&i32
但是有一个
x
是
Box<i32>
,你可以使用
&*x
进行reborrow,这实际上是两个操作的组合,使用
*
解引用
Box
并使用
&
对其内容进行新的引用。
Box
需要很多编译器魔法才能实现这一点。
因此,人们的想法是:如果我们允��任何人决定他们的类型在
*
解引用时的行为,我们将减少自定义指针(如
Box
、
Rc
、
Arc
)所需的魔法,从而允许在库中定义新的指针类型……于是
Deref
应运而生。
但是,Rust开发人员为了减少令人不悦的重新借用又向前迈了一步。
如果您将&Box<i32>
传递给需要&i32
的函数,则可能需要执行以下操作(顺便说一下,这仍然可以编译):
fn main() {
let a = &Box::new(2);
test(&**a);
}
fn test(a: &i32) { println!("{}", *a) }
所以(好的Rust开发人员)为什么不自动化再借用,并让人们只写
test(a)
呢?
当有一个类型
T
(例如
Box<i32>
)解引用为
U
(
i32
)时,我们将允许
&T
(
&Box<i32>
)"强制转换"为
&U
(
&i32
)。
这个你可能会认识为当前的强制转换规则。但编译器执行的所有魔法都是通过需要时撒上
*
(调用
deref
)来尝试再次借用。实际上更像是一个小魔术表演。
现在回到将
AnyType<&String>
转换为
AnyType<&str>
的问题。正如我们所见,编译器并没有想象中那么神奇,对
&*AnyType<&String>
进行解引用和重新借用操作并不能得到预期的结果。即使您设法为
AnyType
实现
Deref
并且让
*AnyType<&String>
解引用为
AnyType<str>
,重新借用结果仍然会产生
&AnyType<str>
,而不是
AnyType<&str>
。
因此,Rust 的强制转换机制无法用于此。您需要明确告诉 Rust 如何将
&String
从
AnyType
中取出并放回一个
&str
。
作为您特定情况的一种解决方法,如果您的函数使用
Option<&str>
仅限于一个并且在您的控制下,则可以将其概括为使用
Option<T>
,其中
T:AsRef<str>
,像这样:
fn test<T: AsRef<str>>(o: Option<T>) {
if let Some(s) = o { println!("{}", s.as_ref()) }
}
(*) Rust开发者明确反对魔法,那些麻瓜!
map
或其他东西手动进行转换)。所以是的,我仍然有“为什么”的问题。我已经编辑了问题,重点关注这个方面。谢谢! - Bill Fraser