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


85

通常,我是Option<String>从计算中获得的,我想使用此值或默认的硬编码值。

使用整数将是微不足道的:

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

但是使用aString和a &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>以避免这种分配。

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

Answers:


66

从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不起作用。对于代码Options<String>as_deref变体,我不了解第一个以及引用它的副本会发生什么。下面是使用我的工作代码as_dereflet 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行得通,因为它仍然创建对所Option<String>返回的原始.serial_number数据的引用,因此仍指向该所有者拥有的数据Optionas_deref相对于在闭包的主体中进行转换(放弃返回的切片的源),进行转换的地方是否有所不同?如果是这样,是否有办法解决?喜欢返回str的副本吗?
Paul-Sebastian Manole

@ Paul-SebastianManole请阅读现有的答案,其中显示了如何使用map;这样可以解决您的问题吗?
Shepmaster '20

是的,但我仍然感到困惑。
Paul-Sebastian Manole


55

您可以使用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()隐式接受on的引用opt,给出一个&Option<String>(因为as_ref()takes &self,即它接收到一个引用),然后将其转换为Option<&String>。然后,我们map将其转换为Option<&str>。这是做什么的&**x:最右边的*(首先评估)仅取消引用&String,得到一个String左值。然后,最左边的*实际上调用该Deref特征,因为String 实现了 Deref<Target=str>,为我们提供了一个str左值。最后,&接受str左值的地址,给我们一个&str

您可以通过使用map_or合并mapunwrap_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特征):

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

13
bikesheddy评论:代替map(|x| &**x)您也可以这样做map(String::as_ref)
fjh

2
尽管仍然有些冗长,但效果很好。为了明确起见,opt.as_ref()is的类型Option<&String>,然后opt.as_ref().map(String::as_ref)将其传递&StringString::as_ref,然后返回&str。为什么不能将&StringfromOption::as_ref强制转换为an &str
乔治·希利亚德

1
@thirtythreefortyString::as_ref返回an &str,而不是a &&String(请参见此处
fjh 2015年

@fjh我才意识到这一点,并进行了编辑:)。我的强制性问题仍然存在。
乔治·希利亚德

1
@ little-dude:在中&**,最左侧*实际上是调用Dereftrait,因为String实现了Deref<Target=str>Deref::derefAsRef::as_ref两者都提供参考到引用转换,并且它发生从转换&String&str可用与两者。您可以使用map(String::deref)代替map(String::as_ref),它也将是等效的。
弗朗西斯·加涅

20

更好的方法是将其通用地用于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>上的函数,是标准库的一部分!
比尔·弗雷泽

1
谢谢!这对我有用-如果我包含此代码,则opt.as_deref()确实是Option<&str>
rspeer '16

7

尽管我喜欢Veedrac的答案(我曾经用过),但是如果您只需要一点,并且希望表达一些东西,则可以使用as_ref()map并进行String::as_str链接:

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

assert_eq!(Some("some value"), opt.as_ref().map(String::as_str));

1

这是您可以做到的一种方法。请记住,您必须保留原件String,否则会&str被切成薄片吗?

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

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

围栏


2
但这并没有变成Option <&str>。
rspeer '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.