Conversión de Option <String> a Option <& str>

85

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?

George Hilliard
fuente

Respuestas:

66

A partir de Rust 1.40, la biblioteca estándar tiene Option::as_derefque hacer esto:

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_deref().unwrap_or("default string");
}
Pastor
fuente
Funciona para mí, pero no puedo entender por qué mi otra versión mapno funcionó. No entiendo qué pasa con el primero Options<String>y la copia que lo hace referencia en el caso de la as_derefvariante del código. Aquí está mi código de trabajo usando as_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 intento 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
Mi error es 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 original Option<String>devuelto por .serial_number, de modo que todavía apunta a los datos que pertenecen a eso Option? ¿Es la diferencia que as_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?
Paul-Sebastian Manole
@ Paul-SebastianManole, lea la respuesta existente que muestra cómo usarmap ; ¿Eso resuelve tu problema?
Shepmaster
Sí, pero todavía estoy perplejo.
Paul-Sebastian Manole
@ Pablo-SebastianManole ¿qué tal Resultado de la Opción :: mapa no vive lo suficiente
Shepmaster
55

Puede usar as_ref()y map()para transformar un Option<String>en un 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()implícitamente toma una referencia opt, dando un &Option<String>(porque as_ref()toma &self, es decir, recibe una referencia), y la convierte en un Option<&String>. Luego usamos mappara convertirlo en un Option<&str>. &**xEsto es lo que hace: el de la derecha *(que se evalúa primero) simplemente elimina la referencia &String, dando un Stringlvalue. Entonces, el más a la izquierda *invoca realmente el Derefrasgo, porque String implementa Deref<Target=str> , dándonos un valor strl. Finalmente, &toma la dirección del strlvalue, dándonos un &str.

Puede simplificar esto un poco más usando map_orpara combinar mapy unwrap_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 su String::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(del AsRefrasgo, 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 el Derefrasgo):

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 necesidades Option<&str>sin envolver &strpermanezcan disponibles. Si eso es demasiado complicado, podría 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));
}
Francis Gagné
fuente
13
comentario de bikesheddy: en lugar de map(|x| &**x)tú también podrías hacer map(String::as_ref).
fjh
2
Esto funciona muy bien, aunque todavía un poco detallado. Para aclarar, el tipo de opt.as_ref()es Option<&String>, luego opt.as_ref().map(String::as_ref)pasa esto &Stringa String::as_ref, que devuelve un &str. ¿Por qué no puede el &Stringde Option::as_refser forzados a una &str?
George Hilliard
1
@thirtythreeforty String::as_refdevuelve an &str, no a &&String(ver aquí )
fjh
@fjh Me acabo de dar cuenta de eso y lo edité :). Mi pregunta sobre la coacción permanece.
George Hilliard
1
@ little-dude: En &**, el de más a la izquierda *invoca el Derefrasgo, porque Stringimplements Deref<Target=str>. Deref::derefy AsRef::as_refambos proporcionan conversiones de referencia a referencia, y sucede que la conversión de &Stringa &strestá disponible con ambos. Podría usar en map(String::deref)lugar de map(String::as_ref), también sería equivalente.
Francis Gagné
20

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.

Veedrac
fuente
1
Esta es una función de gran utilidad. ¡Ojalá fuera parte de la biblioteca estándar como una función en la Opción <T>!
Bill Fraser
1
¡Gracias! Esto funciona para mí: si incluyo este código, entonces opt.as_deref()es un Option<&str>.
rspeer
7

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 la String::as_strcadena:

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

assert_eq!(Some("some value"), opt.as_ref().map(String::as_str));
Michele d'Amico
fuente
1

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

Jorge Israel Peña
fuente
2
Eso no lo convirtió en una opción <& str>.
rspeer