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 Stringa &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?

mapno funcionó. No entiendo qué pasa con el primeroOptions<String>y la copia que lo hace referencia en el caso de laas_derefvariante 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_dereffunciona, 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_derefhace 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 usamosmappara convertirlo en unOption<&str>.&**xEsto es lo que hace: el de la derecha*(que se evalúa primero) simplemente elimina la referencia&String, dando unStringlvalue. Entonces, el más a la izquierda*invoca realmente elDerefrasgo, porqueStringimplementaDeref<Target=str>, dándonos un valorstrl. Finalmente,&toma la dirección delstrlvalue, dándonos un&str.Puede simplificar esto un poco más usando
map_orpara combinarmapyunwrap_oren 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
&**xparece demasiado mágico, puedes escribir en suString::as_strlugar: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(delAsRefrasgo, 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 elDerefrasgo):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&strpermanezcan 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&StringaString::as_ref, que devuelve un&str. ¿Por qué no puede el&StringdeOption::as_refser forzados a una&str?String::as_refdevuelve an&str, no a&&String(ver aquí )&**, el de más a la izquierda*invoca elDerefrasgo, porqueStringimplementsDeref<Target=str>.Deref::derefyAsRef::as_refambos proporcionan conversiones de referencia a referencia, y sucede que la conversión de&Stringa&strestá 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(),mapy laString::as_strcadena: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
Stringen todo, de lo contrario ¿cuál sería el&strser 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