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 viene deprecato?
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 viene deprecato?
Risposte:
String
è il tipo di stringa heap dinamico, ad esempio Vec
: utilizzalo quando devi possedere o modificare i dati della stringa.
str
è una sequenza immutabile di 1 byte UTF-8 di lunghezza dinamica da qualche parte nella memoria. Poiché la dimensione è sconosciuta, è possibile gestirla solo dietro un puntatore. Ciò significa che il str
più delle volte 2 appare come &str
: un riferimento ad alcuni dati UTF-8, normalmente chiamati "slice slice" o semplicemente "slice". Una sezione è solo una vista su alcuni dati e tali dati possono essere ovunque, ad es
"foo"
è a &'static str
. I dati vengono hardcoded nell'eseguibile e caricati in memoria all'avvio del programma.String
: String
dereferenze a una &str
vista dei String
dati.Sullo stack : ad esempio, quanto segue crea un array di byte allocato nello stack e 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 sintesi, utilizzare String
se sono necessari dati di stringa di proprietà (come passare stringhe ad altri thread o crearli in fase di esecuzione) e utilizzare &str
se è necessaria solo la visualizzazione di una stringa.
Questo è identico alla relazione tra un vettore Vec<T>
e una sezione &[T]
ed è simile alla relazione tra valore per T
e riferimento &T
per i tipi generali.
1 A str
è a lunghezza fissa; non è possibile scrivere byte oltre la fine o lasciare byte finali non validi. Poiché UTF-8 è una codifica a larghezza variabile, ciò costringe effettivamente tutti gli str
s a essere immutabili in molti casi. In generale, la mutazione richiede la scrittura di un numero maggiore o minore di byte rispetto a prima (ad esempio, la sostituzione di un a
(1 byte) con un ä
(2+ byte) richiederebbe di creare più spazio nel str
). Ci sono metodi specifici che possono modificare un &str
posto, soprattutto quelli che gestiscono solo caratteri ASCII, come make_ascii_uppercase
.
2 I tipi di dimensioni dinamiche consentono cose come Rc<str>
per una sequenza di byte UTF-8 contati di riferimento da Rust 1.2. Rust 1.21 consente di creare facilmente questi tipi.
[u8; N]
.
Rc<str>
e Arc<str>
ora sono utilizzabili tramite la libreria standard.
Ho un background C ++ e ho trovato molto utile pensare String
e &str
in termini C ++:
String
è come una std::string
; possiede la memoria e fa il lavoro sporco di gestione della memoria.&str
è come una char*
(ma un po 'più sofisticata); ci indica l'inizio di un blocco nello stesso modo in cui è possibile ottenere un puntatore al contenuto di std::string
.Uno di loro sparirà? 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. È possibile cercare, dividere, analizzare e persino sostituire i blocchi senza dover allocare nuova memoria.
&str
può guardare all'interno di a String
in quanto può indicare una stringa letterale. Il codice seguente deve copiare la stringa letterale nella String
memoria gestita:
let a: String = "hello rust".into();
Il seguente codice ti consente di usare il valore letterale stesso senza copia (leggi solo però)
let a: &str = "hello rust";
str
, usato solo come &str
, è una porzione di stringa, un riferimento a una matrice di byte UTF-8.
String
è quello che era ~str
un array di byte UTF-8 di proprietà coltivabile.
~str
adesso èBox<str>
~str
era coltivabile mentre Box<str>
non è coltivabile. (Quello ~str
ed ~[T]
erano magicamente coltivabili, a differenza di qualsiasi altro oggetto ~
, era esattamente il motivo String
e Vec<T>
furono introdotti, in modo che le regole fossero tutte semplici e coerenti.)
In realtà sono completamente diversi. Prima di tutto, a str
non è altro che una cosa a livello di tipo; può essere ragionato solo a livello di tipo perché è un cosiddetto tipo di dimensioni dinamiche (DST). La dimensione che str
occupa non può essere conosciuta al momento della compilazione e dipende dalle informazioni di runtime - non può essere memorizzata in una variabile perché il compilatore deve sapere in fase di compilazione quale sia la dimensione di ciascuna variabile. A str
è concettualmente solo una riga di u8
byte con la garanzia che forma UTF-8 valido. Quanto è grande la fila? Nessuno lo sa fino al runtime, quindi non può essere archiviato in una variabile.
La cosa interessante è che una &str
o qualsiasi altro puntatore a una str
come Box<str>
fa esistere in fase di esecuzione. Questo è un cosiddetto "puntatore grasso"; è un puntatore con informazioni extra (in questo caso la dimensione dell'oggetto a cui punta), quindi è due volte più grande. In effetti, a &str
è abbastanza vicino a a String
(ma non a a &String
). A &str
è due parole; un puntatore a un primo byte di un str
e un altro numero che descrive quanti byte sono lunghi str
.
Contrariamente a quanto detto, a str
non deve essere immutabile. Se riesci a ottenere un &mut str
puntatore esclusivo a str
, puoi mutarlo e tutte le funzioni sicure che lo mutano garantiscono che il vincolo UTF-8 sia rispettato perché se viene violato abbiamo un comportamento indefinito poiché la biblioteca presume che questo vincolo sia vero e non lo controlla.
Quindi cos'è un String
? Sono tre parole; due sono uguali a &str
ma aggiunge una terza parola che è la capacità del str
buffer sull'heap, sempre sull'heap (a str
non è necessariamente sull'heap) che gestisce prima di essere riempito e deve riassegnare. il String
fondo possiede un str
come si dice; lo controlla e può ridimensionarlo e riallocarlo quando lo ritiene opportuno. Quindi a String
è come detto più vicino a &str
che a str
.
Un'altra cosa è a Box<str>
; anche questo possiede un str
e la sua rappresentazione di runtime è la stessa di un &str
ma possiede anche il str
contrario del &str
ma non può ridimensionarlo perché non conosce la sua capacità, quindi sostanzialmente Box<str>
può essere visto come una lunghezza fissa String
che non può essere ridimensionata (puoi convertilo sempre in a String
se vuoi ridimensionarlo).
Esiste una relazione molto simile tra [T]
e Vec<T>
tranne che non esiste alcun vincolo UTF-8 e può contenere qualsiasi tipo le cui dimensioni non sono dinamiche.
L'uso str
a livello di tipo serve principalmente a creare astrazioni generiche con &str
; esiste a livello di tipo per poter scrivere comodamente i tratti. In teoria str
, una cosa tipo non aveva bisogno di esistere e solo, &str
ma ciò avrebbe significato scrivere un sacco di codice extra che ora può essere generico.
&str
è super utile per poter avere più sottostringhe diverse di a String
senza dover copiare; come detto a String
possiede l' str
heap che gestisce e se si potesse creare solo una sottostringa di a String
con una nuova String
, sarebbe necessario copiarla perché tutto in Rust può avere un solo proprietario per gestire la sicurezza della memoria. Quindi, 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 sottostringhe diverse str
della stessa stringa. string
è quello che possiede il str
buffer completo effettivo sull'heap e le &str
sottostringhe sono solo puntatori di grasso a quel buffer sull'heap.
std::String
è semplicemente un vettore di u8
. Puoi trovarne la definizione nel codice sorgente . È allocato in heap e coltivabile.
#[derive(PartialOrd, Eq, Ord)]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct String {
vec: Vec<u8>,
}
str
è un tipo primitivo, chiamato anche stringa slice . Una sezione di stringa ha dimensioni fisse. Una stringa letterale come let test = "hello world"
ha &'static str
tipo. test
è un riferimento a questa stringa allocata staticamente.
&str
non può essere modificato, ad esempio,
let mut word = "hello world";
word[0] = 's';
word.push('\n');
str
ha una sezione mutabile &mut str
, ad esempio:
pub fn split_at_mut(&mut self, mid: usize) -> (&mut str, &mut str)
let mut s = "Per Martin-Löf".to_string();
{
let (first, last) = s.split_at_mut(3);
first.make_ascii_uppercase();
assert_eq!("PER", first);
assert_eq!(" Martin-Löf", last);
}
assert_eq!("PER Martin-Löf", s);
Ma una piccola modifica a UTF-8 può cambiare la sua lunghezza in byte e una sezione non può riallocare il suo referente.
In parole semplici, il String
tipo di dati è memorizzato sull'heap (proprio come Vec
) e hai accesso a quella posizione.
&str
è un tipo di fetta. Ciò significa che è solo un riferimento a un già presente String
da qualche parte nell'heap.
&str
non esegue alcuna allocazione in fase di esecuzione. Quindi, per ragioni di memoria, è possibile utilizzare &str
sopra String
. Tuttavia, tieni presente che durante l'utilizzo &str
potresti dover affrontare vite esplicite.
str
è view
già presente String
nell'heap.
Per persone C # e Java:
String
===StringBuilder
&str
stringa di Rust === (immutabile)Mi piace pensare a &str
come una vista su una stringa, come una stringa internata in Java / C # dove non è possibile cambiarla, ma crearne una nuova.
Ecco una spiegazione semplice e veloce.
String
- Una struttura di dati allocabile heap allocabile e coltivabile. Può essere costretto a &str
.
str
- è (ora, man mano che Rust si evolve) stringa mutabile di lunghezza fissa che vive nell'heap o nel binario. È possibile interagire solo str
come tipo preso in prestito tramite una vista di tipo stringa, ad esempio &str
.
Considerazioni sull'uso:
Preferisci String
se vuoi possedere o mutare una stringa - come passare la stringa a un altro thread, ecc.
Preferisci &str
se vuoi avere una vista di sola lettura di una stringa.
&str
è composto da due componenti: un puntatore ad alcuni byte e una lunghezza".