Quelles sont les différences entre le `String` et le` str` de Rust?

421

Pourquoi Rust a String- strt-il et ? Quelles sont les différences entre Stringet str? Quand utilise-t-on à la Stringplace de stret vice versa? L'un d'eux est-il devenu obsolète?

Daniel Fath
la source

Réponses:

492

Stringest le type de chaîne de tas dynamique, comme Vec: utilisez-le lorsque vous devez posséder ou modifier vos données de chaîne.

strest une séquence immuable de 1 octet UTF-8 de longueur dynamique quelque part en mémoire. La taille étant inconnue, on ne peut la gérer que derrière un pointeur. Cela signifie que le strplus souvent 2 apparaît comme &str: une référence à certaines données UTF-8, normalement appelées "tranche de chaîne" ou simplement "tranche". Une tranche est juste une vue sur certaines données, et ces données peuvent être n'importe où, par exemple

  • Dans le stockage statique : un littéral de chaîne "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.
  • A l'intérieur d'un tas allouéString : Stringdéréférences à une &strvue des Stringdonnées de.
  • Sur la pile : par exemple, ce qui suit crée un tableau d'octets alloué par pile, puis obtient une vue de ces données sous forme de&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 Stringsi vous avez besoin de données de chaîne détenues (comme passer des chaînes à d'autres threads ou les créer lors de l'exécution) et utilisez &strsi vous n'avez besoin que 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 par valeur Tet par référence &Tpour les types généraux.


1 A strest de longueur fixe; vous ne pouvez pas écrire d'octets au-delà de la fin ou laisser des octets de fin non valides. Étant donné que UTF-8 est un codage à largeur variable, cela oblige effectivement tous les strs à être immuables dans de nombreux cas. En général, la mutation nécessite d'écrire plus ou moins d'octets qu'auparavant (par exemple, remplacer un a(1 octet) par un ä(2+ octets) nécessiterait de faire plus de place dans le str). Il existe des méthodes spécifiques qui peuvent modifier un &stren place, principalement celles qui ne gèrent que des caractères ASCII, comme make_ascii_uppercase.

2 Les types de taille dynamique permettent des choses comme Rc<str>une séquence d'octets UTF-8 comptés par référence depuis Rust 1.2. Rust 1.21 permet de créer facilement ces types.

huon
la source
10
"séquence d'octets UTF-8 ( de longueur inconnue )" - est-ce obsolète? Les documents indiquent que «A &strest composé de deux composants: un pointeur sur certains octets et une longueur».
mrec
11
Ce n'est pas obsolète (cette représentation a été assez stable), juste un peu imprécis: ce n'est pas statiquement connu, contrairement à, disons [u8; N].
Huon
2
@mrec il est inconnu au moment de la compilation, des hypothèses sur sa taille ne peuvent pas être faites, par exemple, lors de la création d'un cadre de pile. C'est pourquoi il est souvent traité comme une référence, dont une référence est une taille connue au moment de la compilation, qui est la taille d'un pointeur.
Sekhat
1
Mise à jour: Rc<str>et Arc<str>sont désormais utilisables via la bibliothèque standard.
Centril
1
@cjohansson Les objets alloués statiquement ne sont normalement stockés ni sur le tas, ni sur la pile, mais dans leur propre région de mémoire.
Brennan Vincent
97

J'ai une formation en C ++ et j'ai trouvé très utile de penser Stringet &stren termes C ++:

  • Une rouille Stringest comme un std::string; il possède la mémoire et fait le sale boulot de gérer la mémoire.
  • Une rouille &strest comme un char*(mais un peu plus sophistiqué); il 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 fins:

Stringconserve le tampon et est très pratique à utiliser. &strest léger et doit être utilisé pour "regarder" les chaînes. Vous pouvez rechercher, fractionner, analyser et même remplacer des morceaux sans avoir à allouer de nouvelle mémoire.

&strpeut regarder à l'intérieur d'un Stringcar il peut pointer vers un littéral de chaîne. Le code suivant doit copier la chaîne littérale dans la Stringmémoire gérée:

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";
Luis Ayuso
la source
13
comme un string_view?
Abhinav Gauniyal
2
Oui comme string_view mais intrinsèque au langage et correctement emprunté vérifié.
locka
41

str, utilisé uniquement en tant que &str, est une tranche de chaîne, une référence à un tableau d'octets UTF-8.

Stringest ce qui était autrefois ~str, un tableau d'octets UTF-8 évolutif et détenu.

Chris Morgan
la source
Techniquement, ce qui était autrefois ~strest maintenantBox<str>
jv110
3
@ jv110: non, car il ~strétait possible de le développer alors qu'il Box<str>ne l'est pas. (Cela ~stret qui ~[T]pouvaient être développés par magie, contrairement à tout autre ~objet, était exactement pourquoi Stringet a Vec<T>été introduit, de sorte que les règles étaient toutes simples et cohérentes.)
Chris Morgan
18

Ils sont en fait complètement différents. Tout d'abord, a strn'est rien d'autre qu'une chose de niveau 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 strutilisée 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 au moment de la compilation quelle est la taille de chaque variable. A strn'est conceptuellement qu'une ligne d' u8octets avec la garantie qu'il forme un UTF-8 valide. Quelle est la taille de la rangée? Personne ne sait jusqu'à l'exécution donc il ne peut pas être stocké dans une variable.

La chose intéressante est qu'un &strou tout autre pointeur vers un strcomme Box<str> fait exist lors de l' exécution. Il s'agit d'un soi-disant "gros pointeur"; c'est un pointeur avec des informations supplémentaires (dans ce cas, la taille de la chose qu'il pointe), il est donc deux fois plus grand. En fait, un &strest assez proche d'un String(mais pas d'un &String). A &strest deux mots; un pointeur vers le premier octet de a stret un autre nombre qui décrit le nombre d'octets de la longueur str.

Contrairement à ce qui est dit, a strn'a pas besoin d'être immuable. Si vous pouvez obtenir un &mut stren tant que pointeur exclusif vers le str, vous pouvez le muter et toutes les fonctions sûres qui le mutent garantissent que la contrainte UTF-8 est respectée car si cela est violé, nous avons un comportement indéfini car la bibliothèque suppose que cette contrainte est vrai et ne le vérifie pas.

Alors qu'est-ce qu'un String? Ça fait trois mots; deux sont les mêmes que pour &strmais il ajoute un troisième mot qui est la capacité du strtampon sur le tas, toujours sur le tas (a strn'est pas nécessairement sur le tas) qu'il gère avant qu'il ne soit rempli et qu'il doive le réallouer. le possède enString gros un comme on dit; il le contrôle et peut le redimensionner et le réallouer quand bon lui semble. Donc, comme dit plus près de a que de a .strString&strstr

Une autre chose est un Box<str>; il possède également un stret sa représentation d'exécution est identique à un &strmais il possède également le strcontrairement au &strmais il ne peut pas le redimensionner car il ne connaît pas sa capacité, donc en gros un Box<str>peut être considéré comme une longueur fixe Stringqui ne peut pas être redimensionnée (vous pouvez convertissez-le toujours en un Stringsi vous voulez 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'elle peut contenir n'importe quel type dont la taille n'est pas dynamique.

L'utilisation de strau niveau du type consiste principalement à créer des abstractions génériques avec &str; il existe au niveau du type pour pouvoir facilement écrire des traits. En théorie str, une chose de type n'avait pas besoin d'exister et seulement &strmais cela signifierait que beaucoup de code supplémentaire devrait être écrit qui peut maintenant être générique.

&strest super utile pour pouvoir avoir plusieurs sous-chaînes différentes d'un Stringsans avoir à copier; comme dit a String possède le strsur le tas qu'il gère et si vous ne pouviez créer qu'une sous-chaîne d'un Stringavec un nouveau, Stringil devrait être copié 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 strde la même chaîne. stringest celui qui possède le strtampon complet réel sur le tas et les &strsous-chaînes ne sont que de gros pointeurs vers ce tampon sur le tas.

Zorf
la source
4

std::Stringest simplement un vecteur de u8. Vous pouvez trouver sa définition dans le code source . Il est alloué en tas et évolutif.

#[derive(PartialOrd, Eq, Ord)]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct String {
    vec: Vec<u8>,
}

strest un type primitif, également appelé tranche de chaîne . Une tranche de chaîne a une taille fixe. Une chaîne littérale comme let test = "hello world"has &'static strtype. testest une référence à cette chaîne allouée statiquement. &strne peut pas être modifié, par exemple,

let mut word = "hello world";
word[0] = 's';
word.push('\n');

stra 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 un petit changement en UTF-8 peut changer sa longueur d'octet, et une tranche ne peut pas réallouer son référent.

Apérion
la source
0

En termes simples, le Stringtype de données est-il stocké sur le tas (comme Vec), et vous avez accès à cet emplacement.

&strest un type de tranche. Cela signifie qu'il s'agit simplement d'une référence à un élément déjà présent Stringquelque part dans le tas.

&strne fait aucune allocation lors de l'exécution. Donc, pour des raisons de mémoire, vous pouvez utiliser &strover String. Mais gardez à l'esprit que lors de l'utilisation, &strvous devrez peut-être gérer des durées de vie explicites.

00imvj00
la source
1
quelque part en tas - ce n'est pas tout à fait exact.
Shepmaster
Ce que je voulais dire, c'est qu'il strest viewdéjà présent Stringen tas.
00imvj00
1
Je comprends que c'est ce que vous vouliez dire, et je dis que ce n'est pas tout à fait exact. Le "tas" n'est pas une partie obligatoire de l'instruction.
Shepmaster
-1

Pour les personnes C # et Java:

  • Rouille ' String===StringBuilder
  • La &str chaîne === (immuable) de Rust

J'aime penser à un &strcomme une vue sur une chaîne, comme une chaîne internée en Java / C # où vous ne pouvez pas la changer, n'en créer qu'une nouvelle.

Écureuil
la source
1
La plus grande différence entre les chaînes Java / C # et les chaînes Rust est que Rust garantit que la chaîne est unicode correcte, car obtenir le troisième caractère d'une chaîne nécessite plus de réflexion que simplement "abc" [2]. (Étant donné que nous vivons dans un monde multilingue, c'est une bonne chose.)
Squirrel
C'est incorrect . Le sujet de la mutabilité est déjà abordé dans la réponse la plus votée; veuillez le lire pour en savoir plus.
Shepmaster
-5

Voici une explication rapide et facile.

String- Une structure de données évolutive et propriétaire pouvant être allouée en tas. Il peut être contraint à un &str.

str- est (maintenant, au fur et à mesure que Rust évolue) une chaîne mutable de longueur fixe qui vit sur le tas ou dans le binaire. Vous pouvez uniquement interagir avec strun type emprunté via une vue de tranche de chaîne, telle que &str.

Considérations d'utilisation:

Préférez Stringsi vous voulez posséder ou muter une chaîne - comme passer la chaîne à un autre thread, etc.

Préférez &strsi vous voulez avoir une vue en lecture seule d'une chaîne.

Développeur
la source
C'est incorrect . Le sujet de la mutabilité est déjà abordé dans la réponse la plus votée; veuillez le lire pour en savoir plus.
Shepmaster