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

146

经常情况下,我从计算中获取一个Option<String>,我想要使用这个值或者一个默认硬编码的值。

如果是整数,这将非常简单:

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

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

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

这里的确切错误是:

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`

一种选项是将字符串切片转换为拥有所有权的字符串(即 String),正如 rustc 建议的那样:

let value = opt.unwrap_or("default string".to_string());

但这会导致一次分配,当我希望立即将结果转换回字符串切片时,这是不可取的,就像在对Regex::new()进行调用时一样:

let rx: Regex = Regex::new(&opt.unwrap_or("default string".to_string()));

我更愿意将Option<String>转换为Option<&str>,以避免此分配。

写这个的惯用方式是什么?


“which is undesirable” -> 这真的是不可取的吗?你的内存受到了严重限制吗?你需要将每个CPU周期推到极限吗?如果这两者中有任何一项是正确的,那么你正在使用错误的语言。"default string".to_string()) 可能是正确的解决方案,因为它使你的代码易于理解,这比节省100 ns的CPU时间或100字节的内存更重要。 - FreelanceConsultant
5个回答

163

从 Rust 1.40 开始,标准库拥有 Option::as_deref 方法来实现此功能:

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

另请参阅:


它对我有效,但我不明白为什么我的另一个使用 map 的版本不起作用。我不理解在代码的 as_deref 变体中发生了什么,第一个 Options<String> 和引用它的副本的情况。这是我的使用 as_deref 的工作代码:let device_id = UsbDeviceIdentifier::VidPidSn { vid: device.vendor_id, pid: device.product_id, sn: device.serial_number.as_deref().unwrap_or("") };和我的第一次尝试:let device_id = UsbDeviceIdentifier::VidPidSn { vid: device.vendor_id, pid: device.product_id, sn: device.serial_number.map(|s| s.as_str()).unwrap_or("") }; - Paul-Sebastian Manole
我的错误是error[E0515]: cannot return value referencing function parameter \s`, s.as_str() returns a value referencing data owned by the current function. 好的,但为什么as_deref可以工作呢?因为它仍然创建了一个对.serial_number返回的原始Option<String>的引用,所以它仍然指向由该Option拥有的数据!?区别在于as_deref`是否在闭包体内进行转换,而不是在闭包体外进行转换并且返回切片的源。如果是这样,有没有办法解决这个问题?比如返回字符串的副本? - Paul-Sebastian Manole
@Paul-SebastianManole 请阅读展示如何使用map的现有答案; 这能解决你的问题吗? - Shepmaster
是的,但我仍然感到困惑。 - Paul-Sebastian Manole
@Paul-SebastianManole,关于Option::map的结果存活时间不够长,你怎么看? - Shepmaster
2
有趣的是,我无法使用as_deref()Option<&String>转换为Option<&str>。(Option<&String>是通过哈希表查找获得的。)当然,.map(String::as_str)可以工作,但我想知道是否有一个“标准”的组合器来实现相同的功能。例如,.copied().as_deref()也不起作用,因为Option<&String>不是Copy,而.cloned().as_deref()可以工作,但需要进行克隆。 - user4815162342

68
您可以使用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");
}

首先,as_ref() 隐式地对 opt 取引用,得到一个 &Option<String> (因为 as_ref() 接受 &self ,即它接收一个引用),并将其转换为 Option<&String>。然后我们使用 map 将其转换为 Option<&str>。下面是 &**x 的执行过程:最右边的 *(首先被评估)只是简单地取消引用 &String,得到一个 String 左边的 * 实际上调用了Deref trait,因为 String 实现了 Deref<Target=str>,从而得到一个 str lvalue。最后,& 取出了 str lvalue 的地址,得到一个 &str
你可以通过使用map_ormapunwrap_or结合成单个操作来进一步简化此过程。
fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_ref().map_or("default string", |x| &**x);
}

如果 &**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);
}

或者使用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);
}

或者使用 String::deref (但您也需要导入Deref trait):

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

要让这两种方式都起作用,您需要保持对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));
}

14
可以使用map(String::as_ref)代替map(|x| &**x) - fjh
2
这个代码运行得很好,虽然还是有点啰嗦。为了澄清,opt.as_ref() 的类型是 Option<&String>,然后 opt.as_ref().map(String::as_ref) 将这个 &String 传递给 String::as_ref,它返回一个 &str。为什么 Option::as_ref 中的 &String 不能强制转换为 &str - George Hilliard
1
@thirtythreeforty String::as_ref 返回的是一个 &str,而不是一个 &&String(参见这里 - fjh
@fjh我刚意识到并进行了编辑:). 我的强制转换问题仍然存在。 - George Hilliard
1
@little-dude:在&**中,最左边的*实际上调用了Deref特质,因为String实现了Deref<Target=str>Deref::derefAsRef::as_ref都提供了引用到引用的转换,而将&String转换为&str也可以使用这两个方法。你可以使用map(String::deref)代替map(String::as_ref),它们是等效的。 - Francis Gagné
显示剩余5条评论

26

更好的方法是通用实现 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)
    }
}

这实际上是将as_ref进行了泛化。


1
这是一个很棒的实用函数。我希望它能作为 Option<T> 的标准库函数存在! - Bill Fraser
1
谢谢!这对我有用——如果我包含这段代码,那么opt.as_deref()确实是一个Option<&str> - rspeer

10

尽管我喜欢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));

1
这是一种实现方式。请记住,您必须保留原始的字符串,否则&str将成为什么样的切片呢?

let opt = Some(String::from("test")); // kept around

let unwrapped: &str = match opt.as_ref() {
  Some(s) => s, // deref coercion
  None => "default",
};

playpen


2
那并没有将它转换为Option<&str>。 - rspeer

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