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

问题描述 投票:4回答:1

以下是我可以使用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的参数类型之间存在差异。

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

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

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

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

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

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

generics rust option rust-result
1个回答
2
投票

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

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

Playground link

如果foo(b);没有注释,它会告诉你它预期String,得到str

match语句返回&str(从&String转换),但unwrap_or期望与你正在调用的Option相同的类型,&String&str不能没有手动更改(String::fromto_stringto_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);
}

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(在关闭结束时删除引用)。

© www.soinside.com 2019 - 2024. All rights reserved.