ps4 - rust traduccion
Conversión de la opción<String> a la opción<& str> (5)
Aquí hay una manera de hacerlo.
Tenga en cuenta que debe mantener la
String
original, de lo contrario, ¿en qué
&str
sería una porción?
let opt = Some(String::from("test")); // kept around
let unwrapped: &str = match opt.as_ref() {
Some(s) => s, // deref coercion
None => "default",
};
Muy a menudo he obtenido una
Option<String>
de un cálculo, y me gustaría usar este valor o un valor predeterminado codificado.
Esto sería trivial con un número entero:
let opt: Option<i32> = Some(3);
let value = opt.unwrap_or(0); // 0 being the default
Pero con
String
y a
&str
, el compilador se queja de tipos no coincidentes:
let opt: Option<String> = Some("some value".to_owned());
let value = opt.unwrap_or("default string");
El error exacto aquí es:
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`
Una opción es convertir el segmento de cadena en una cadena propia, como lo sugiere rustc:
let value = opt.unwrap_or("default string".to_string());
Pero esto provoca una asignación, lo que no es deseable cuando quiero convertir de inmediato el resultado en un segmento de cadena, como en esta llamada a
Regex::new()
:
let rx: Regex = Regex::new(&opt.unwrap_or("default string".to_string()));
Prefiero convertir la
Option<String>
en una
Option<&str>
para evitar esta asignación.
¿Cuál es la forma idomática de escribir esto?
Aunque me encanta la respuesta de Veedrac (la usé), si la necesita en un punto y desea algo expresivo, puede usar
as_ref()
,
map
y
String::as_str
chain:
let opt: Option<String> = Some("some value".to_string());
assert_eq!(Some("some value"), opt.as_ref().map(String::as_str));
La biblioteca estándar tiene la función inestable nocturna solamente
Option::deref
para hacer esto:
#![feature(inner_deref)]
fn main() {
let opt: Option<String> = Some("some value".to_owned());
let value = opt.deref().unwrap_or("default string");
}
Puede usar
as_ref()
y
map()
para transformar una
Option<String>
en una
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");
}
Primero,
as_ref()
toma implícitamente una referencia en
opt
, dando una
&Option<String>
(porque
as_ref()
toma
&self
, es decir, recibe una referencia), y la convierte en una
Option<&String>
.
Luego usamos
map
para convertirlo en una
Option<&str>
.
Esto es lo que hace
&**x
: el más a la derecha
*
(que se evalúa primero) simplemente elimina la referencia de
&String
, dando un valor de
String
.
Luego, el
*
más a la izquierda en realidad invoca el rasgo
Deref
, porque
String
implements
Deref<Target=str>
, dándonos un valor
str
.
Finalmente,
&
toma la dirección del valor
str
, dándonos un
&str
.
Puede simplificar esto un poco más usando
map_or
para combinar
map
y
unwrap_or
en una sola operación:
fn main() {
let opt: Option<String> = Some("some value".to_owned());
let value = opt.as_ref().map_or("default string", |x| &**x);
}
Si
&**x
parece demasiado mágico, puedes escribir
String::as_str
en
String::as_str
lugar:
fn main() {
let opt: Option<String> = Some("some value".to_owned());
let value = opt.as_ref().map_or("default string", String::as_str);
}
o
String::as_ref
(del rasgo
AsRef
, que está en el
prelude
):
fn main() {
let opt: Option<String> = Some("some value".to_owned());
let value = opt.as_ref().map_or("default string", String::as_ref);
}
o
String::deref
(aunque también necesita importar el rasgo
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);
}
Para que cualquiera de estos funcione, debe mantener un propietario para la
Option<String>
siempre que la
Option<&str>
o sin envolver
&str
deba permanecer disponible.
Si eso es demasiado complicado, podrías usar
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));
}
Una mejor manera podría ser implementar esto genéricamente para
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)
}
}
que efectivamente generaliza
as_ref
.