为什么Option<&String>不能转换为Option<&str>?

12

我有一个Option<String>和一个接收Option<&str>的函数。我以为Option::as_ref会起作用,因为通常&String会自动转换为&str,但这里不行。我收到以下错误:

sandbox.rs:6:27: 6:37 error: mismatched types:
 expected `core::option::Option<&str>`,
    found `core::option::Option<&collections::string::String>`
(expected str,
    found struct `collections::string::String`) [E0308]
虽然这个答案描述了如何从一个类型转换为另一个类型,但我仍然想知道为什么&String没有像通常那样被"强制转换"(如果这是正确的术语)成&str

感谢您指出其他问题。它回答了我的“如何”问题(不幸的是,看起来没有内置的方法可以做到这一点;您必须使用map或其他东西手动进行转换)。所以是的,我仍然有“为什么”的问题。我已经编辑了问题,重点关注这个方面。谢谢! - Bill Fraser
3个回答

20
您的期望是合理的。如果编译器有魔法可以将&String转换为&str,那么它应该也能将Option<&String>转换为Option<&str>或者(同样地)将AnyType<&String>转换为AnyType<&str>
但是,在这种情况下,编译器的魔力非常有限。矛盾的是,强制转换是从试图减少编译器中的魔力而出现的(*)。要理解这一点,您需要了解强制转换和重新借用之间的联系,并跟随我进行一次小小的 Rust 历史探究。
前段时间,在Rust中你经常会看到这样的结构:&*x:一个“reborrow”。对于像Box这样的指针类型,你希望能够用*来解引用它们以获取内部的值。如果你需要一个&i32但是有一个xBox<i32>,你可以使用&*x进行reborrow,这实际上是两个操作的组合,使用*解引用Box并使用&对其内容进行新的引用。Box需要很多编译器魔法才能实现这一点。
因此,人们的想法是:如果我们允��任何人决定他们的类型在*解引用时的行为,我们将减少自定义指针(如BoxRcArc)所需的魔法,从而允许在库中定义新的指针类型……于是Deref应运而生。

但是,Rust开发人员为了减少令人不悦的重新借用又向前迈了一步。 如果您将&Box<i32>传递给需要&i32的函数,则可能需要执行以下操作(顺便说一下,这仍然可以编译):

fn main() {
    let a = &Box::new(2);
    test(&**a); // one * to dereference the Box, 
                // one * to dereference the &
                // reborrow with &
}

fn test(a: &i32) { println!("{}", *a) }

所以(好的Rust开发人员)为什么不自动化再借用,并让人们只写test(a)呢?
当有一个类型T(例如Box<i32>)解引用为Ui32)时,我们将允许&T&Box<i32>)"强制转换"为&U&i32)。
这个你可能会认识为当前的强制转换规则。但编译器执行的所有魔法都是通过需要时撒上*(调用deref)来尝试再次借用。实际上更像是一个小魔术表演。
现在回到将 AnyType<&String> 转换为 AnyType<&str> 的问题。正如我们所见,编译器并没有想象中那么神奇,对 &*AnyType<&String> 进行解引用和重新借用操作并不能得到预期的结果。即使您设法为 AnyType 实现 Deref 并且让 *AnyType<&String> 解引用为 AnyType<str>,重新借用结果仍然会产生 &AnyType<str>,而不是 AnyType<&str>
因此,Rust 的强制转换机制无法用于此。您需要明确告诉 Rust 如何将 &StringAnyType 中取出并放回一个 &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开发者明确反对魔法,那些麻瓜!


5
有两种在Rust中常用的强制类型转换:自动引用和自动解引用。
自动引用只能将 `T` 转换为 `&T` 或 `&mut T`,所以在这里并不相关。
自动解引用会自动调用 `Deref::deref`。 `Option` 没有实现 `Deref`,因此无法使用此方法。然而,`String` 实现了 `deref` 以将其转换为 `&str`,这就是为什么您可以看到这种强制类型转换。
我们知道 `Option` 没有实现 `Deref`,因为 `Deref :: deref` 返回具有与 `&self` 相同生命周期的指针,指针的生命周期要求意味着指针必须与 `&self` 一样长寿,因此除了少数情况外,指针必须是某些预先存在的对象而不是在 `deref` 调用中创建的对象。
这并不意味着上述是它没有实现 `Deref` 的唯一原因,尽管这足以说明问题。

4
Rust语言通过“Deref”特性定义了一种将“&T”隐式转换为“&U”的方式,适用于特定的“T”和“U”对。重要的是要理解,在某些情况下,“&U”是一个构造值。例如,既没有“String”也没有“&String”包含“&str”,因此“deref()”方法需要手动创建“&str”。
然而,该语言定义一种将“X<&T>”隐式转换为“X<&U>”的方式。为了使其工作,编译器必须知道一种初始化“X<&U>”的方法,给定“X<&T>”(对于不同的“X”值,这种方法将是不同的)。你不能简单地获取“X<&T>”的二进制表示,并将其重新解释为“X<&U>”。实际上,“Option<&String>”和“Option<&str>”甚至都不具有相同的大小!即使大小匹配,指针通常也不相同。
为了开发出一个通用的解决方案来解决这个问题,Rust需要实现高阶类型(在Rust 1.6中不可用)。也可以针对该特定问题提供一种临时解决方案,但当HKT被实现时,它将变得多余。

我真的不明白需要什么高阶类型。当然,一个人只需要一个名为DerefValue的函数,它直接返回DerefValue::Target,而不是作为引用返回。只有在想要限制可能的解引用类型时才需要HKTs,这似乎完全违背了所建议的泛化思想。 - Veedrac
我想象了一个特质 Convert,你可以在像 Option 这样的类型构造器上实现它,提供一个单一的方法 convert,该方法接受两个泛型参数 TU,其参数将是一个 Self<&T>,其结果将是一个 Self<&U>,其中 T: Deref<Target=U> - Francis Gagné
是的,但这对你有什么好处呢?不能通过这种方式将Option<Option<T>>转换为Option<Option<&T>>,也不能将BitVec转换为BitSlice,但它确实允许为Vec实现它,尽管这可能是一个坏主意。如果您没有限制函数实际可以执行的操作(如Deref所做的那样),我真的不明白您强制进行转换以成为这种特定类型函数会带来什么好处。 - Veedrac
我认为这是不支持这种隐式转换的好动力。 :) - Francis Gagné

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接