Pourquoi Rust a-t-il String
et str
? Quelles sont les différences entre String
et str
? Quand utilise-t-on String
au lieu de str
et vice versa? Est-ce que l'un d'eux devient obsolète?
String
est le type de chaîne dynamique du tas, tel que Vec
: utilisez-le lorsque vous devez posséder ou modifier vos données de chaîne.
str
est un immuable1 séquence d'octets UTF-8 de longueur dynamique quelque part en mémoire. Comme la taille est inconnue, on ne peut la manipuler que derrière un pointeur. Cela signifie que str
le plus souvent2 Apparaît sous la forme &str
: référence à certaines données UTF-8, normalement appelée "tranche de chaîne" ou simplement "tranche". Une tranche est simplement une vue de certaines données, et ces données peuvent être n'importe où, par exemple.
"foo"
est un &'static str
. Les données sont codées en dur dans l'exécutable et chargées en mémoire lors de l'exécution du programme.String
: String
DEREFERENCES À UNE VUE &str
des données de la String
.Sur la pile: par exemple. Ce qui suit crée un tableau d'octets alloué à la pile, puis obtient une vue de ces données sous la forme &str
:
use std::str;
let x: &[u8] = &[b'a', b'b', b'c'];
let stack_str: &str = str::from_utf8(x).unwrap();
En résumé, utilisez String
si vous avez besoin de données de chaîne possédées (telles que le passage de chaînes à d'autres threads ou leur construction à l'exécution), et &str
si vous avez uniquement besoin d'une vue d'une chaîne.
Ceci est identique à la relation entre un vecteur Vec<T>
et une tranche &[T]
et est similaire à la relation entre valeurT
et référence&T
pour les types généraux.
1 Une str
est de longueur fixe; vous ne pouvez pas écrire d'octets au-delà de la fin ni laisser des octets non valides à la fin. UTF-8 étant un codage à largeur variable, cela force effectivement toutes les str
s à rester immuables dans de nombreux cas. En général, une mutation nécessite d'écrire plus ou moins d'octets qu'auparavant (par exemple, remplacer une a
(1 octet) par un ä
(plus de 2 octets) nécessiterait de laisser plus de place à la str
). Certaines méthodes spécifiques peuvent modifier un &str
à la place, principalement celles qui gèrent uniquement les caractères ASCII, comme make_ascii_uppercase
.
2 Les types de taille dynamique autorisent des choses comme Rc<str>
pour une séquence de références comptées en octets UTF-8 depuis Rust 1.2. Rust 1.21 permet de créer facilement ces types.
J'ai un background C++ et j'ai trouvé très utile de penser à String
et &str
en termes C++:
String
est comme un std::string
; il possède la mémoire et fait le sale boulot de la gestion de la mémoire.&str
est comme un char*
(mais un peu plus sophistiqué); cela nous indique le début d'un morceau de la même manière que vous pouvez obtenir un pointeur sur le contenu de std::string
.Est-ce que l'un d'eux va disparaître? Je ne pense pas. Ils servent deux objectifs:
String
conserve le tampon et est très pratique à utiliser. &str
est léger et doit être utilisé pour "regarder" dans les chaînes. Vous pouvez rechercher, scinder, analyser et même remplacer des morceaux sans avoir à allouer de la mémoire.
&str
peut regarder à l'intérieur d'une String
car il peut pointer sur un littéral de chaîne. Le code suivant doit copier la chaîne littérale dans la mémoire gérée String
:
let a: String = "hello Rust".into();
Le code suivant vous permet d'utiliser le littéral lui-même sans copie (en lecture seule cependant)
let a: &str = "hello Rust";
Ils sont en fait complètement différents. Tout d’abord, une str
n’est rien d’autre qu’une chose de type; il ne peut être raisonné qu'au niveau du type car il s'agit d'un type dit de taille dynamique (DST). La taille occupée par str
ne peut pas être connue au moment de la compilation et dépend des informations d'exécution. Elle ne peut pas être stockée dans une variable car le compilateur doit savoir à la compilation quelle est la taille de chaque variable. Une str
n'est théoriquement qu'une rangée de u8
octets avec la garantie qu'elle constitue un UTF-8 valide. Quelle est la taille de la rangée? Personne ne le sait avant l'exécution, il ne peut donc pas être enregistré dans une variable.
La chose intéressante est qu'un &str
ou tout autre pointeur sur une str
comme Box<str>
existe existe au moment de l'exécution. C'est ce qu'on appelle un "gros pointeur"; c'est un pointeur avec des informations supplémentaires (dans ce cas, la taille de la chose sur laquelle il pointe), il est donc deux fois plus grand. En fait, un &str
est assez proche d'un String
(mais pas d'un &String
). Un &str
est deux mots; un pointeur sur le premier octet d'un str
et un autre nombre décrivant le nombre d'octets longs du str
.
Contrairement à ce qui est dit, une str
n'a pas besoin d'être immuable. Si vous pouvez obtenir un &mut str
en tant que pointeur exclusif sur le str
, vous pouvez le muter et toutes les fonctions de sécurité qui le miment garantissent que la contrainte UTF-8 est respectée, car elle est violée. ont un comportement indéfini car la bibliothèque suppose que cette contrainte est vraie et ne la vérifie pas.
Alors qu'est-ce qu'un String
? C'est trois mots; deux sont les mêmes que pour &str
mais il ajoute un troisième mot qui est la capacité du tampon str
sur le tas, toujours sur le tas (un str
n'est pas nécessairement sur le tas ) il gère avant qu'il soit rempli et doit être réalloué. la String
possède fondamentalement possède a str
comme on dit; il le contrôle et peut le redimensionner et le réaffecter quand bon lui semble. Ainsi, une String
est plus proche d'un &str
que d'un str
.
Une autre chose est un Box<str>
; ceci possède également un str
et sa représentation d'exécution est identique à un &str
mais il possède également le str
contrairement au &str
mais il ne peut pas le redimensionner car il ne le sait pas sa capacité, donc un Box<str>
peut être considéré comme un String
de longueur fixe qui ne peut pas être redimensionné (vous pouvez toujours le convertir en String
si vous souhaitez le redimensionner).
Une relation très similaire existe entre [T]
et Vec<T>
sauf qu'il n'y a pas de contrainte UTF-8 et qu'il peut contenir tout type dont la taille n'est pas dynamique.
L'utilisation de str
au niveau du type sert principalement à créer des abstractions génériques avec &str
; il existe au niveau des types pour pouvoir écrire facilement des traits. En théorie, str
en tant que chose type n'a pas besoin d'exister et seul &str
mais cela signifierait qu'il faudrait écrire beaucoup de code supplémentaire qui peut maintenant être générique.
&str
est super utile pour pouvoir avoir plusieurs sous-chaînes différentes d'un String
sans avoir à copier; comme dit un String
possède le str
sur le tas qu'il gère et si vous pouviez seulement créer une sous-chaîne d'un String
avec un nouveau String
il aurait à copier car tout dans Rust ne peut avoir qu'un seul propriétaire pour gérer la sécurité de la mémoire. Ainsi, par exemple, vous pouvez couper une chaîne:
let string: String = "a string".to_string();
let substring1: &str = &string[1..3];
let substring2: &str = &string[2..4];
Nous avons deux sous-chaînes différentes str
s de la même chaîne. string
est celui qui possède le tampon complet str
présent sur le tas et les sous-chaînes &str
ne sont que de gros pointeurs vers ce tampon sur le tas.
Pour les personnes C # et Java:
String
=== StringBuilder
&str
=== (immuable) de RustJ'aime penser à &str
comme à une vue sur une chaîne, comme une chaîne internée en Java/C # où vous ne pouvez pas la modifier, créez-en une nouvelle.
std::String
est simplement un vecteur de u8
. Vous pouvez trouver sa définition dans code source . C'est un tas alloué et qui peut être développé.
#[derive(PartialOrd, Eq, Ord)]
#[stable(feature = "Rust1", since = "1.0.0")]
pub struct String {
vec: Vec<u8>,
}
str
est un type primitif, également appelé string slice. Une tranche de chaîne a une taille fixe. Une chaîne littérale telle que let test = "hello world"
a le type &'static str
. test
est une référence à cette chaîne allouée de manière statique. &str
ne peut pas être modifié, par exemple,
let mut Word = "hello world";
Word[0] = 's';
Word.Push('\n');
str
a une tranche mutable &mut str
, par exemple: 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);
Mais une petite modification apportée à UTF-8 peut changer sa longueur en octets, et une tranche ne peut pas réaffecter son référent.
En termes simples, String
est le type de données stocké sur le tas (tout comme Vec
), et vous avez accès à cet emplacement.
&str
est un type de tranche. Cela signifie qu'il fait simplement référence à une String
déjà présente quelque part dans le tas.
&str
ne fait aucune allocation au moment de l'exécution. Donc, pour des raisons de mémoire, vous pouvez utiliser &str
sur String
. Cependant, gardez à l'esprit que lorsque vous utilisez &str
, vous devrez peut-être gérer des durées de vie explicites.