Perché è sconsigliato accettare un riferimento a String (& String), Vec (& Vec) o Box (& Box) come argomento di una funzione?


127

Ho scritto del codice Rust che prende &Stringcome argomento:

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

Ho anche scritto codice che prende in riferimento a un Veco Box:

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

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

Tuttavia, ho ricevuto feedback sul fatto che farlo in questo modo non è una buona idea. Perchè no?

Risposte:


162

TL; DR: si può invece utilizzare &str, &[T]o &Tper consentire un codice più generico.


  1. Uno dei motivi principali per utilizzare a Stringo a Vecè perché consentono di aumentare o diminuire la capacità. Tuttavia, quando si accetta un riferimento immutabile, non è possibile utilizzare nessuno di questi metodi interessanti su Veco String.

  2. L'accettazione di un &String, &Veco richiede&Box anche l'allocazione dell'argomento nell'heap prima di poter chiamare la funzione. L'accettazione di a consente una stringa letterale (salvata nei dati del programma) e l'accettazione di o consente un array o una variabile allocati nello stack. L'allocazione non necessaria è una perdita di prestazioni. Questo di solito viene esposto immediatamente quando provi a chiamare questi metodi in un test o in un metodo:&str&[T]&Tmain

    awesome_greeting(&String::from("Anna"));
    total_price(&vec![42, 13, 1337])
    is_even(&Box::new(42))
  3. Un'altra considerazione sulle prestazioni è quella &String, &Vece &Boxintrodurre un livello di riferimento indiretto non necessario in quanto devi dereferenziare &Stringper ottenere un Stringe quindi eseguire un secondo dereferenziamento in cui finire &str.

Invece, dovresti accettare una stringa slice ( &str), slice ( &[T]) o solo un riferimento ( &T). A &String, &Vec<T>o &Box<T>verrà automaticamente costretto a &str, &[T]o &T, rispettivamente.

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
}

Ora puoi chiamare questi metodi con un insieme più ampio di tipi. Ad esempio, awesome_greetingpuò essere chiamato con una stringa letterale ( "Anna") o allocata String. total_pricepuò essere chiamato con un riferimento a un array ( &[1, 2, 3]) o un allocato Vec.


Se desideri aggiungere o rimuovere elementi da Stringo Vec<T>, puoi prendere un riferimento modificabile ( &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);
}

In particolare per le sezioni, puoi anche accettare un &mut [T]o &mut str. Ciò consente di modificare un valore specifico all'interno della sezione, ma non è possibile modificare il numero di elementi all'interno della sezione (il che significa che è molto limitato per le stringhe):

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();
    }
}

5
Che ne dici di un tl; dr all'inizio? Questa risposta è già un po 'lunga. Qualcosa come " &strè più generale (come in: impone meno restrizioni) senza capacità ridotte"? Inoltre: il punto 3 spesso non è così importante credo. Di solito Vecs e Strings vivranno nello stack e spesso anche da qualche parte vicino allo stack frame corrente. Lo stack è solitamente caldo e il dereferenziamento sarà servito da una cache della CPU.
— Lukas Kalbertodt

3
@Shepmaster: per quanto riguarda il costo di allocazione, potrebbe valere la pena menzionare il problema particolare delle sottostringhe / sezioni quando si parla di allocazione obbligatoria. total_price(&prices[0..4])non richiede l'assegnazione di un nuovo vettore per la sezione.
— Matthieu M.

4
Questa è un'ottima risposta. Ho appena iniziato con Rust e mi stavo impegnando a capire quando dovrei usare a &stre perché (provenendo da Python, quindi di solito non mi occupo esplicitamente dei tipi).
— Ha chiarito

2
Suggerimenti fantastici sui parametri. Serve solo un dubbio: "Accettare una & String, & Vec o & Box richiede anche un'allocazione prima di poter chiamare il metodo." ... Perché è così? Potresti per favore indicare la parte nella documentazione dove posso leggere questo in dettaglio? (Sono un mendicante). Inoltre, possiamo avere suggerimenti simili sui tipi di reso?
— Nawaz

2
Mancano informazioni sul motivo per cui è necessaria un'allocazione aggiuntiva. La stringa è memorizzata nell'heap, quando si accetta & String come argomento, perché Rust non si limita a passare un puntatore memorizzato nello stack che punta allo spazio dell'heap, non capisco perché il passaggio di una & String avrebbe bisogno di un'allocazione aggiuntiva, passando una stringa slice dovrebbe anche richiedere l'invio di un puntatore memorizzato nello stack che punta allo spazio heap?
— cjohansson

22

Oltre alla risposta di Shepmaster , un altro motivo per accettare un &str(e allo stesso modo &[T]ecc.) È a causa di tutti gli altri tipi oltre String e &strche soddisfano anche Deref<Target = str>. Uno degli esempi più notevoli è Cow<str>, che ti consente di essere molto flessibile sul fatto che tu abbia a che fare con dati di proprietà o presi in prestito.

Se hai:

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

Ma devi chiamarlo con a Cow<str>, dovrai farlo:

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());

Quando si modifica il tipo di argomento in &str, è possibile utilizzarlo Cowsenza interruzioni, senza alcuna allocazione non necessaria, proprio come 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);

L'accettazione &strrende la chiamata alla funzione più uniforme e comoda e il modo "più semplice" è ora anche il più efficiente. Questi esempi funzioneranno anche con Cow<[T]>ecc.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.