¿Por qué no se recomienda aceptar una referencia a String (& String), Vec (& Vec) o Box (& Box) como argumento de función?

127

Escribí un código de Rust que toma &Stringcomo argumento:

fn awesome_greeting(name: &String) {
    println!("Wow, you are awesome, {}!", name);
}

También he escrito código que toma una referencia a Veco Box:

fn total_price(prices: &Vec<i32>) -> i32 {
    prices.iter().sum()
}

fn is_even(value: &Box<i32>) -> bool {
    **value % 2 == 0
}

Sin embargo, recibí algunos comentarios de que hacerlo así no es una buena idea. Por qué no?

Pastor
fuente

Respuestas:

162

TL; DR: En su lugar &str, se puede usar &[T]o &Tpermitir un código más genérico.


  1. Una de las principales razones para utilizar a Stringo a Veces porque permiten aumentar o disminuir la capacidad. Sin embargo, cuando acepta una referencia inmutable, no puede utilizar ninguno de esos métodos interesantes en Veco String.

  2. Aceptar un &String, &Veco &Boxtambién requiere que el argumento se asigne en el montón antes de poder llamar a la función. Aceptar un &strpermite un literal de cadena (guardado en los datos del programa) y aceptar un &[T]o &Tpermite una matriz o variable asignada a la pila. La asignación innecesaria es una pérdida de rendimiento. Esto generalmente se expone de inmediato cuando intenta llamar a estos métodos en una prueba o un mainmétodo:

    awesome_greeting(&String::from("Anna"));
    total_price(&vec![42, 13, 1337])
    is_even(&Box::new(42))
  3. Otra consideración de rendimiento es eso &String, &Vece &Boxintroducir una capa innecesaria de indirección, ya que debe eliminar la referencia &Stringpara obtener una Stringy luego realizar una segunda desreferencia para terminar en &str.

En su lugar, debe aceptar una cadena de segmento ( &str), un segmento ( &[T]) o simplemente una referencia ( &T). A &String, &Vec<T>o &Box<T>será automáticamente coaccionado a a &str, &[T]o &T, respectivamente.

fn awesome_greeting(name: &str) {
    println!("Wow, you are awesome, {}!", name);
}
fn total_price(prices: &[i32]) -> i32 {
    prices.iter().sum()
}
fn is_even(value: &i32) -> bool {
    *value % 2 == 0
}

Ahora puede llamar a estos métodos con un conjunto más amplio de tipos. Por ejemplo, awesome_greetingse puede llamar con una cadena literal ( "Anna") o un archivo String. total_pricese puede llamar con una referencia a una matriz ( &[1, 2, 3]) o un archivo Vec.


Si desea agregar o eliminar elementos del Stringo Vec<T>, puede tomar una referencia mutable ( &mut Stringo &mut Vec<T>):

fn add_greeting_target(greeting: &mut String) {
    greeting.push_str("world!");
}
fn add_candy_prices(prices: &mut Vec<i32>) {
    prices.push(5);
    prices.push(25);
}

Específicamente para sectores, también puede aceptar &mut [T]o &mut str. Esto le permite mutar un valor específico dentro del sector, pero no puede cambiar la cantidad de elementos dentro del sector (lo que significa que está muy restringido para las cadenas):

fn reset_first_price(prices: &mut [i32]) {
    prices[0] = 0;
}
fn lowercase_first_ascii_character(s: &mut str) {
    if let Some(f) = s.get_mut(0..1) {
        f.make_ascii_lowercase();
    }
}
Pastor
fuente
5
¿Qué tal un tl; dr al principio? Esta respuesta ya es algo larga. ¿Algo como " &stres más general (como en: impone menos restricciones) sin capacidades reducidas"? Además, creo que el punto 3 no suele ser tan importante. Por lo general, Vecsy Strings vivirán en la pila y, a menudo, incluso cerca del marco de pila actual. La pila suele estar caliente y la desreferenciación se realizará desde una memoria caché de la CPU.
Lukas Kalbertodt
3
@Shepmaster: Con respecto al costo de asignación, podría valer la pena mencionar el problema particular de las subcadenas / porciones cuando se habla de asignación obligatoria. total_price(&prices[0..4])no requiere la asignación de un nuevo vector para el segmento.
Matthieu M.
4
Esta es una respuesta genial. Estoy empezando en Rust y estaba recibiendo atado averiguar cuando debería usar una &stry por eso (que viene de Python, así que por lo general no se ocupan explícitamente de tipos). Aclaró todo eso perfectamente
C.Nivs
2
Increíbles consejos sobre parámetros. Solo necesito una duda: "Aceptar un & String, & Vec o & Box también requiere una asignación antes de poder llamar al método" ... ¿Por qué es así? ¿Podría señalar la parte de los documentos donde puedo leer esto en detalle? (Soy un principiante). Además, ¿podemos tener sugerencias similares sobre los tipos de devolución?
Nawaz
2
Me falta información sobre por qué se requiere una asignación adicional. La cadena se almacena en el montón, al aceptar & String como argumento, ¿por qué Rust no pasa simplemente un puntero almacenado en la pila que apunta al espacio del montón? No entiendo por qué pasar una & String necesitaría una asignación adicional, pasando una cadena el segmento también debería requerir el envío de un puntero almacenado en la pila que apunte al espacio del montón?
cjohansson
22

Además de la respuesta de Shepmaster , otra razón para aceptar a &str(y de manera similar, &[T]etc.) es porque todos los otros tipos además String y &streso también satisfacen Deref<Target = str>. Uno de los ejemplos más notables es el Cow<str>que le permite ser muy flexible sobre si se trata de datos propios o prestados.

Si usted tiene:

fn awesome_greeting(name: &String) {
    println!("Wow, you are awesome, {}!", name);
}

Pero debes llamarlo con a Cow<str>, tendrás que hacer esto:

let c: Cow<str> = Cow::from("hello");
// Allocate an owned String from a str reference and then makes a reference to it anyway!
awesome_greeting(&c.to_string());

Cuando cambia el tipo de argumento a &str, puede usarlo Cowsin problemas, sin ninguna asignación innecesaria, al igual que con String:

let c: Cow<str> = Cow::from("hello");
// Just pass the same reference along
awesome_greeting(&c);

let c: Cow<str> = Cow::from(String::from("hello"));
// Pass a reference to the owned string that you already have
awesome_greeting(&c);

Aceptar &strhace que llamar a su función sea más uniforme y conveniente, y la forma "más fácil" ahora también es la más eficiente. Estos ejemplos también funcionarán con Cow<[T]>etc.

Peter Hall
fuente