Преобразование из опции <String> в Option <& str>
Очень часто я получал 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'
Один из вариантов - преобразовать фрагмент строки в принадлежащую строку, как предполагает 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>
, чтобы избежать этого распределения.
Что за нелепый способ написать это?
Ответы
Ответ 1
Вы можете использовать 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
l. Затем самый левый *
фактически вызывает черту Deref
, потому что String
реализует Deref<Target=str>
, давая нам значение str
l. Наконец, &
получает адрес str
lvalue, давая нам &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);
}
Если &**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));
}
Ответ 2
Лучше всего реализовать это в целом для 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
.
Ответ 3
В стандартной библиотеке есть нестабильная ночная функция 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");
}
Ответ 4
Хотя я люблю ответ 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));
Ответ 5
Вот один из способов сделать это. Имейте в виду, что вам нужно сохранить исходный String
, в противном случае, что бы &str
был фрагментом?
let opt = Some(String::from("test")); // kept around
let unwrapped: &str = match opt.as_ref() {
Some(s) => s, // deref coercion
None => "default",
};
playpen