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.