Quali sono le differenze tra `String` e` str` di Rust?

Perché Rust ha String e str ? Quali sono le differenze tra String e str ? Quando si usa String invece di str e viceversa? Uno di loro sta diventando deprecato?

String è il tipo di stringa heap dinamico, come Vec : Vec quando devi possedere o modificare i tuoi dati di stringa.

str è una immutabile sequenza di byte UTF-8 di lunghezza dynamic da qualche parte nella memoria. Poiché la dimensione è sconosciuta, è ansible gestirla solo dietro un puntatore. Ciò significa che lo str più comunemente 2 appare come &str : un riferimento ad alcuni dati UTF-8, normalmente chiamati “string slice” o semplicemente “slice”. Una fetta è solo una vista su alcuni dati, e che i dati possono essere ovunque, ad es

  • nell’archiviazione statica: una stringa letterale "foo" è una stringa "foo" &'static str . I dati sono codificati nel file eseguibile e caricati in memoria quando viene eseguito il programma.
  • all’interno di un heap assegnato String : dereferenze di String a una vista &str dei dati di String .
  • sullo stack: ad es. quanto segue crea un array di byte allocato allo stack, quindi ottiene una visualizzazione di tali dati come &str :

     use std::str; let x: &[u8] = &[b'a', b'b', b'c']; let stack_str: &str = str::from_utf8(x).unwrap(); 

In breve, usa String se hai bisogno di dati di stringa di proprietà (come passare stringhe ad altre attività o costruirle in fase di runtime) e usa &str se hai solo bisogno di vedere una stringa.

Questo è identico alla relazione tra un vettore Vec e una slice &[T] , ed è simile alla relazione tra il valore-by T e il riferimento-by &T per i tipi generali.


1 A str è una lunghezza fissa; non è ansible scrivere byte oltre la fine o lasciare byte finali non validi. Poiché UTF-8 è una codifica a larghezza variabile, ciò impone in modo efficace tutti gli str s di essere immutabili. In generale, la mutazione richiede di scrivere più o meno byte rispetto a prima (es. Sostituire un a (1 byte) con un ä (2+ byte) richiederebbe più spazio nello str ).

2 Al momento può apparire solo come &str , ma i tipi di dimensioni dinamiche possono consentire cose come Rc per una sequenza di byte UTF-8 conteggiati di riferimento. Potrebbe anche non str , str non si adatta perfettamente allo schema DST, dato che non esiste ancora una versione a dimensione fissa.

Ho uno sfondo C ++ e ho trovato molto utile pensare a String e &str nei termini C ++:

  • Una String rust è come una std::string ; possiede la memoria e fa il lavoro sporco di gestire la memoria.
  • A Rust &str è come un char* (ma un po ‘più sofisticato); ci indica all’inizio di un blocco nello stesso modo in cui puoi ottenere un puntatore al contenuto di std::string .

L’uno o l’altro spariranno? Io non la penso così. Servono a due scopi:

String mantiene il buffer ed è molto pratico da usare. &str è leggero e dovrebbe essere usato per “guardare” nelle stringhe. È ansible cercare, dividere, analizzare e persino sostituire i blocchi senza la necessità di allocare nuova memoria.

&str può guardare all’interno di una String poichè può puntare a qualche stringa letterale. Il seguente codice deve copiare la stringa letterale nella memoria gestita da String :

 let a: String = "hello rust".into(); 

Il seguente codice ti consente di usare il letterale stesso senza copiare (leggi solo se)

 let a: &str = "hello rust"; 

str , usato solo come &str , è una slice a stringa, un riferimento a un array di byte UTF-8.

String è ciò che era in precedenza ~str , un array di byte UTF-8 coltivabile, di proprietà.

In realtà sono completamente diversi. Prima di tutto, uno str è nient’altro che una cosa di livello tipo; può essere ragionato solo a livello di testo perché è un cosiddetto tipo di dimensione dynamic (DST). La dimensione che occupa lo str non può essere conosciuta in fase di compilazione e dipende dalle informazioni di runtime – non può essere memorizzata in una variabile perché il compilatore deve sapere al momento della compilazione quale sia la dimensione di ogni variabile. Un str è concettualmente solo una riga di u8 byte con la garanzia che formi UTF-8 valido. Quanto è grande la fila? Nessuno sa fino al runtime quindi non può essere memorizzato in una variabile.

La cosa interessante è che a &str o qualsiasi altro puntatore a un str come Box esiste al runtime. Questo è un cosiddetto “fat pointer”; è un puntatore con informazioni extra (in questo caso la dimensione della cosa a cui punta), quindi è due volte più grande. In effetti, a &str è abbastanza vicino a una String (ma non a una &String ). A &str è due parole; un puntatore ad un primo byte di uno str e un altro numero che descrive quanti byte è lungo lo str .

Contrariamente a quanto si dice, uno str non ha bisogno di essere immutabile. Se riesci a ottenere un &mut str come puntatore esclusivo allo str , puoi mutarlo e tutte le funzioni sicure che lo mutano garantiscono che il vincolo UTF-8 sia mantenuto perché se questo viene violato allora abbiamo un comportamento indefinito come la libreria assume questo vincolo è vero e non lo controlla.

Quindi cos’è una String ? Sono tre parole; due sono gli stessi di &str ma aggiunge una terza parola che è la capacità del buffer str sull’heap, sempre sull’heap (uno str non è necessariamente sull’heap) che gestisce prima che sia riempito e deve ridistribuire . la String possiede fondamentalmente uno str come si dice; lo controlla e può ridimensionarlo e riallocarlo quando lo ritiene opportuno. Quindi una String è come detta più vicina a una &str che a una str .

Un’altra cosa è una Box ; questo possiede anche uno str e la sua rappresentazione in runtime è la stessa di un &str ma possiede anche lo str differenza di &str ma non può ridimensionarlo perché non conosce la sua capacità quindi fondamentalmente una Box può essere vista come una lunghezza String che non può essere ridimensionata (è sempre ansible convertirla in una String se si desidera ridimensionarla).

Esiste una relazione molto simile tra [T] e Vec tranne che non esiste un vincolo UTF-8 e può contenere qualsiasi tipo la cui dimensione non sia dynamic.

L’uso di str a livello di tipo è principalmente per creare astrazioni generiche con &str ; esiste a livello di testo per poter scrivere comodamente tratti. In teoria str come un tipo cosa non ha bisogno di esistere e solo &str ma ciò significherebbe che molto codice aggiuntivo dovrebbe essere scritto che ora può essere generico.

&str è super utile per poter avere più sottostringhe di una String senza dover copiare; come detto una String possiede lo str sull’heap che gestisce e se si potesse creare solo una sottostringa di una String con una nuova String , dovrebbe essere copiata perché tutto in Rust può avere solo un singolo proprietario per gestire la sicurezza della memoria. Ad esempio, puoi tagliare una stringa:

 let string: String = "a string".to_string(); let substring1: &str = &string[1..3]; let substring2: &str = &string[2..4]; 

Abbiamo due stili di sottostringa diversi della stessa stringa. string è quella che possiede il vero e proprio buffer str pieno str e le sottostringhe &str sono solo fat pointer per quel buffer sull’heap.

In parole semplici, String è un tipo di dati memorizzato nell’heap proprio come Vec e si ha accesso al puntatore a quella posizione.

&str è un tipo di sezione. Ciò significa che è solo un riferimento alla String già presente da qualche parte nell’heap.

&str non esegue alcuna allocazione in fase di esecuzione. Quindi, per motivi di memoria puoi usare &str su String . Ma tieni presente che quando usi &str potresti avere a che fare con vite esplicite.