从Option <String>转换为Option <&str>

Geo*_*ard 56 rust

我经常Option<String>从计算中获得一个,我想使用这个值或默认的硬编码值.

这对于一个整数来说是微不足道的:

let opt: Option<i32> = Some(3);
let value = opt.unwrap_or(0); // 0 being the default
Run Code Online (Sandbox Code Playgroud)

但是使用a String和a &str,编译器会抱怨类型不匹配:

let opt: Option<String> = Some("some value".to_owned());
let value = opt.unwrap_or("default string");
Run Code Online (Sandbox Code Playgroud)

这里的确切错误是:

error[E0308]: mismatched types
 --> src/main.rs:4:31
  |
4 |     let value = opt.unwrap_or("default string");
  |                               ^^^^^^^^^^^^^^^^
  |                               |
  |                               expected struct `std::string::String`, found reference
  |                               help: try using a conversion method: `"default string".to_string()`
  |
  = note: expected type `std::string::String`
             found type `&'static str`
Run Code Online (Sandbox Code Playgroud)

一种选择是将字符串切片转换为拥有的String,如rustc所示:

let value = opt.unwrap_or("default string".to_string());
Run Code Online (Sandbox Code Playgroud)

但这会导致分配,当我想立即将结果转换回字符串切片时,这是不合需要的,如此调用Regex::new():

let rx: Regex = Regex::new(&opt.unwrap_or("default string".to_string()));
Run Code Online (Sandbox Code Playgroud)

我宁愿将其转换Option<String>为a Option<&str>来避免这种分配.

写这个的人是什么?

Fra*_*gné 46

您可以使用as_ref()map()转换Option<String>Option<&str>.

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_ref().map(|x| &**x).unwrap_or("default string");
}
Run Code Online (Sandbox Code Playgroud)

首先,as_ref()隐式地接受引用opt,给出&Option<String>(因为as_ref()take &self,即它接收引用),并将其转换为Option<&String>.然后我们用map它来转换为Option<&str>.这是做什么的&**x:最右边*(首先评估)简单地取消引用&String,给出一个String左值.然后,最左边*实际调用Deref特征,因为String 实现 Deref<Target=str>,给我们一个str左值.最后,&获取str左值的地址,给我们一个&str.

您可以在此进一步通过简化位map_or结合map,并unwrap_or在一个单一的操作:

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_ref().map_or("default string", |x| &**x);
}
Run Code Online (Sandbox Code Playgroud)

如果&**x看起来太神奇了,你可以写String::as_str:

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_ref().map_or("default string", String::as_str);
}
Run Code Online (Sandbox Code Playgroud)

String::as_ref(来自AsRef特质,在前奏中):

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_ref().map_or("default string", String::as_ref);
}
Run Code Online (Sandbox Code Playgroud)

或者String::deref(虽然你也需要导入Deref特征):

use std::ops::Deref;

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_ref().map_or("default string", String::deref);
}
Run Code Online (Sandbox Code Playgroud)

要使这些中的任何一个起作用,Option<String>只要需要保持可用Option<&str>或未包装的&str需要,您就需要保留所有者.如果这太复杂了,你可以使用Cow.

use std::borrow::Cow::{Borrowed, Owned};

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.map_or(Borrowed("default string"), |x| Owned(x));
}
Run Code Online (Sandbox Code Playgroud)

  • bikesheddy评论:而不是`map(| x |&**x)`你也可以做`map(String :: as_ref)`. (11认同)
  • 效果很好,尽管还有些冗长。为了明确起见,opt.as_ref()的类型为Option &lt;&String&gt;,然后opt.as_ref()。map(String :: as_ref)将此&String传递给String :: as_ref,返回`&str`。为什么不能将`Option :: as_ref`中的`&String`强制转换为`&str`? (2认同)

Vee*_*rac 19

一个更好的方法可能是实现这一点T: Deref:

use std::ops::Deref;

trait OptionDeref<T: Deref> {
    fn as_deref(&self) -> Option<&T::Target>;
}

impl<T: Deref> OptionDeref<T> for Option<T> {
    fn as_deref(&self) -> Option<&T::Target> {
        self.as_ref().map(Deref::deref)
    }
}
Run Code Online (Sandbox Code Playgroud)

这有效地概括了as_ref.


She*_*ter 15

标准库具有仅在夜间运行的不稳定函数Option::as_deref

#![feature(inner_deref)]

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_deref().unwrap_or("default string");
}
Run Code Online (Sandbox Code Playgroud)

计划在Rust 1.40中稳定该功能。

  • 有趣的是,我无法让 `as_deref()` 将 `Option&lt;&amp;String&gt;` 转换为 `Option&lt;&amp;str&gt;`。(`Option&lt;&amp;String&gt;`是通过哈希表查找获得的。)当然,`.map(String::as_str)`可以工作,但我想知道是否有一个“标准”组合器可以实现相同的效果。例如,`.copied().as_deref()`也不起作用,因为`Option&lt;&amp;String&gt;`不是`Copy`,而`.cloned().as_deref()`可以工作,但代价是克隆。 (2认同)

Mic*_*ico 9

尽管我喜欢 Veedrac 的答案(我使用过它),但如果您在某一时刻需要它并且您想要具有表现力的东西,您可以使用as_ref(),mapString::as_str链:

let opt: Option<String> = Some("some value".to_string());

assert_eq!(Some("some value"), opt.as_ref().map(String::as_str));
Run Code Online (Sandbox Code Playgroud)