Muy a menudo he obtenido un valor Option<String>
de un cálculo, y me gustaría usar este valor o un valor codificado por defecto.
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 ay String
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 sugiere rustc:
let value = opt.unwrap_or("default string".to_string());
Pero esto causa una asignación, lo cual no es deseable cuando quiero convertir inmediatamente el resultado de nuevo 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 el Option<String>
en an Option<&str>
para evitar esta asignación.
¿Cuál es la forma idomática de escribir esto?
map
no funcionó. No entiendo qué pasa con el primeroOptions<String>
y la copia que lo hace referencia en el caso de laas_deref
variante del código. Aquí está mi código de trabajo usandoas_deref
:let device_id = UsbDeviceIdentifier::VidPidSn { vid: device.vendor_id, pid: device.product_id, sn: device.serial_number.as_deref().unwrap_or("") };
y mi primer intentolet device_id = UsbDeviceIdentifier::VidPidSn { vid: device.vendor_id, pid: device.product_id, sn: device.serial_number.map(|s| s.as_str()).unwrap_or("") };
.error[E0515]: cannot return value referencing function parameter `s`
,s.as_str() returns a value referencing data owned by the current function
. Bien, pero ¿por quéas_deref
funciona, porque todavía crea una referencia al originalOption<String>
devuelto por.serial_number
, de modo que todavía apunta a los datos que pertenecen a esoOption
? ¿Es la diferencia queas_deref
hace una transformación en el lugar en lugar de hacerlo en el cuerpo del cierre, donde se descarta la fuente del corte que devuelve? Si es así, ¿hay alguna forma de solucionarlo? Como devolver una copia del str?map
; ¿Eso resuelve tu problema?Puede usar
as_ref()
ymap()
para transformar unOption<String>
en unOption<&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()
implícitamente toma una referenciaopt
, dando un&Option<String>
(porqueas_ref()
toma&self
, es decir, recibe una referencia), y la convierte en unOption<&String>
. Luego usamosmap
para convertirlo en unOption<&str>
.&**x
Esto es lo que hace: el de la derecha*
(que se evalúa primero) simplemente elimina la referencia&String
, dando unString
lvalue. Entonces, el más a la izquierda*
invoca realmente elDeref
rasgo, porqueString
implementaDeref<Target=str>
, dándonos un valorstr
l. Finalmente,&
toma la dirección delstr
lvalue, dándonos un&str
.Puede simplificar esto un poco más usando
map_or
para combinarmap
yunwrap_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 te
&**x
parece demasiado mágico, puedes escribir en suString::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
(delAsRef
rasgo, que está en el preludio ):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 elDeref
rasgo):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 durante el
Option<String>
tiempo que las necesidadesOption<&str>
sin envolver&str
permanezcan disponibles. Si eso es demasiado complicado, podría usarCow
.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)); }
fuente
map(|x| &**x)
tú también podrías hacermap(String::as_ref)
.opt.as_ref()
esOption<&String>
, luegoopt.as_ref().map(String::as_ref)
pasa esto&String
aString::as_ref
, que devuelve un&str
. ¿Por qué no puede el&String
deOption::as_ref
ser forzados a una&str
?String::as_ref
devuelve an&str
, no a&&String
(ver aquí )&**
, el de más a la izquierda*
invoca elDeref
rasgo, porqueString
implementsDeref<Target=str>
.Deref::deref
yAsRef::as_ref
ambos proporcionan conversiones de referencia a referencia, y sucede que la conversión de&String
a&str
está disponible con ambos. Podría usar enmap(String::deref)
lugar demap(String::as_ref)
, también sería equivalente.Una forma más agradable 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
.fuente
opt.as_deref()
es unOption<&str>
.Aunque me encanta la respuesta de Veedrac (lo usé), si lo necesita en un punto, y que le gustaría algo que es expresiva puede utilizar
as_ref()
,map
y laString::as_str
cadena:let opt: Option<String> = Some("some value".to_string()); assert_eq!(Some("some value"), opt.as_ref().map(String::as_str));
fuente
Aquí tienes una forma de hacerlo. Tenga en cuenta que usted tiene que mantener el original
String
en todo, de lo contrario ¿cuál sería el&str
ser una rebanada en?let opt = Some(String::from("test")); // kept around let unwrapped: &str = match opt.as_ref() { Some(s) => s, // deref coercion None => "default", };
corralito
fuente