确定类型时match和unwrap_or之间的区别

时间:2018-03-21 01:45:10

标签: generics rust option rust-result

以下是我可以使用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&str,则None => ""&strSome(name) => name&String,因此它可以成为&str

  2. 如果unwrap_or的参数是&str,而不是将b键入&str,则会看到其中的差异在a中保留的类型(即&String)与unwrap_or的参数类型之间。

  3. 这两种情况之间有什么区别使得类型推导以这种方式工作?

    实现unwrap_or是否接受与选项包含的类型完全相同的类型,而不仅仅是接受它放在匹配中的通用值?

    此外,有没有办法让unwrap_or在这种情况下工作,而不必在外部作用域中声明String或更改选项包装的类型?

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

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

    match相同的效果,比match更明确,更明确地说明类型发生了什么。

1 个答案:

答案 0 :(得分:2)

松散地说,你是正确的一半。

aOption<&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);
}

Playground link

如果取消注释foo(b);,则会告诉您预期Stringstr

match语句返回&str(从&String转换而来),但unwrap_or期望与您Option的{​​{1}}类型相同。重新调用,&String&str无法手动更改String::fromto_stringto_owned

现在,我将试着给为什么做一个简短的解释,但是把它当作一粒盐,因为我不是这个领域的专家

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

然而,String是可修改的。由于我们无法确定字符串中有多少个字符,因此它必须存在于&#34;堆&#34;中,如果某些事物尝试&#,可以移动对象 34;成长为&#34;其他东西,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);
}

Playground link

此代码实际上与您的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(在关闭结束时删除引用)。