5

以下是我可以使用 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`

据我所知,编译器在这里确定类型时的推理如下:

  1. match它确定b是 a的情况下&str,因为None => ""是 a&str并且Some(name) => name是 a&String所以它可以变成 a &str

  2. 如果 to 的参数unwrap_or是 a &str,而不是键入ba ,它会看到(即)中保存的类型和 to 的参数类型&str之间存在差异。a&Stringunwrap_or

这两种使类型推导以这种方式工作的情况有什么区别?

是否被unwrap_or实现为接受与选项包装的类型完全相同的类型,而不是只接受它放置在 match 中的通用值?

此外,在这种情况下,有什么方法可以完成unwrap_or工作,而不必String在外部范围内声明 a 或更改选项包装的类型?

我在其他地方得到的一个让它们工作的答案是:

let b = a.map(|s| s.as_ref()).unwrap_or("")

与 相同的效果match,比match关于类型发生的事情更短,更明确。

4

1 回答 1

2

粗略地说,你说对了一半。

a是一个Option<&String>。An&String可以强制转换为&str,但 an&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语句返回 an &str(从 an 转换而来&String),但期望与您调用的 an 具有unwrap_or相同的类型,如果没有手动更改( , ,等)an就无法变为Option&String&strString::fromto_stringto_owned

现在,我将尝试简要解释为什么会这样,但要持保留态度,因为我不是该领域的专家


当使用字符串文字 ( "foo") 时,程序在编译时会创建一些内存段,以二进制形式表示该文本。这就是为什么字符串文字是 an&str而不仅仅是 a str: 它不是原始文本,而是对启动时已经存在的文本的引用。这意味着字符串文字存在于无法修改的特定内存部分中,因为它们被楔入其他一些对象 - ( "fooquxegg") - 并且尝试将字符添加到字符串文字会导致您覆盖下一个对象。,这是非常糟糕的。

String但是,A是可修改的。由于我们无法确定一个字符串中有多少个字符,它必须存在于“堆”中,如果某物试图“长成”其他东西,则可以String在其中移动对象,并且类型基本上用作指向堆中的那个文本,很像堆中的 aBox<T>T

出于这个原因,虽然 an&String可以成为 an (它指向某处&str的一定数量的文本,在这种情况下,指向堆中的某处而不是静态内存),但不能保证 an 成为 an。如果您将位于无法修改的内存部分中的字符串文字(例如 our )转换为可能被修改的字符串,然后尝试向其附加一些字符(例如),它会覆盖某些内容别的。&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>使用 an 展开的An 。&String这将编译,因为这&String => &str是一个有效的转换,并且编译器将自动插入必要的转换而不需要它是显式的。所以不,unwrap_or没有实现接受完全相同的对象类型;任何可以强制到T(在本例中T为 is &str)的对象都是有效的。

换句话说,您的unwrap_or示例失败是因为an&str不能成为 an&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地图(参考在关闭结束时被删除)。

于 2018-03-21T04:37:42.780 回答