Aller au contenu

Comprendre les string en Rust : String et &str

Comprendre les String en Rust

En Rust, les chaînes de caractères (String) sont un concept clé, mais elles peuvent sembler un peu déroutantes au début en raison de la gestion stricte de la mémoire du langage. Contrairement à d’autres langages où les chaînes sont plus abstraites, Rust fournit deux types principaux pour représenter les chaînes de caractères : String et &str (ou string slice). Dans cet article, nous allons explorer ces deux types, comprendre leurs différences et voir comment les utiliser efficacement.


String et &str : Quelle différence ?

Le type String

Le type String est la structure la plus couramment utilisée pour manipuler des chaînes de caractères dynamiques en Rust. Cela signifie que les chaînes de type String sont stockées sur le tas (heap) et peuvent être redimensionnées. Elles sont mutables, donc tu peux les modifier à volonté (ajouter, retirer des caractères, etc.).

Voici un exemple de création d’une String :

fn main() {
let mut
s = String::from("Bonjour");
s.push_str(", monde !");
println!("{}", s); // Affiche "Bonjour, monde !"
}

Ici, on crée une chaîne de type String avec String::from(), puis on lui ajoute une sous-chaîne avec la méthode push_str().

Le type &str

Le type &str , également appelé slice de chaîne, est une référence immuable à une chaîne de caractères. Les &str sont généralement stockés dans la mémoire du programme (c’est-à-dire le segment de données ou la pile), et leur taille est fixe. Ils sont souvent utilisés pour faire référence à des littéraux de chaîne.

Exemple avec une slice de chaîne :

fn main() {
let s: &str = "Hello, world!";
println!("{}", s); // Affiche "Hello, world!"
}

Dans cet exemple, s est une référence à une chaîne littérale, et sa taille est connue à la compilation.


Créer et manipuler des chaînes

Créer une String

Tu peux créer une String de plusieurs manières :

  • Avec String::new() pour une chaîne vide :
let mut
s = String::new();
  • Avec String::from() pour créer une chaîne à partir d’une slice de chaîne (&str) :
let s = String::from("Rust est cool !");
  • Avec la macro to_string() sur une slice de chaîne :
let s = "Hello".to_string();

Ajouter des caractères à une String

Rust offre plusieurs méthodes pour ajouter des caractères ou des chaînes à une String :

  • push() ajoute un seul caractère :
let mut
s = String::from("Salut");
s.push('!');
println!("{}", s); // Affiche "Salut!"
  • push_str() ajoute une chaîne entière :
let mut
s = String::from("Bonjour");
s.push_str(", monde !");
println!("{}", s); // Affiche "Bonjour, monde !"

Concaténation des chaînes

Tu peux aussi concaténer des String de plusieurs façons. Par exemple, avec l’opérateur + :

let s1 = String::from("Hello");
let s2 = String::from("World");
let s3 = s1 + " " + &s2;
println!("{}", s3); // Affiche "Hello World"

Note que dans cet exemple, s1 est consommée et ne peut plus être utilisée après la concaténation. Cela est dû au fait que l’opérateur + prend possession de la première chaîne.

Si tu veux éviter cela, tu peux utiliser la macro format!() , qui te permet de formater des chaînes sans consommer leurs variables d’origine :

let s1 = String::from("Hello");
let s2 = String::from("World");
let s3 = format!("{} {}", s1, s2);
println!("{}", s3); // Affiche "Hello World"

Slices de chaîne (&str)

Les slices de chaîne sont des références immuables (&) à une partie d’une chaîne, ou à une chaîne entière. Elles sont particulièrement utiles lorsque tu veux passer une partie d’une chaîne à une fonction sans avoir à créer une nouvelle chaîne.

Voici un exemple :

let s = String::from("Rustacean");
let slice = &s[0..4]; // Prend les quatre premiers caractères
println!("{}", slice); // Affiche "Rust"

Les string slices (&str) sont également couramment utilisées comme paramètres de fonction lorsque tu veux accepter des chaînes String et des littéraux (&str).


Gestion de la mémoire et propriété

Propriété des String

En Rust, la propriété des données est cruciale pour comprendre la gestion de la mémoire. Une String possède ses données et est responsable de les libérer lorsqu’elle sort de portée. Par exemple :

fn main() {
let s = String::from("Rust");
prendre_possession(s); // `s` est déplacée
// println!("{}", s); // Erreur : `s` n'est plus valide ici
}
fn prendre_possession(s: String) {
println!("{}", s);
}

Dans cet exemple, la fonction prendre_possession() prend la propriété de la chaîne s, donc elle n’est plus accessible après l’appel de la fonction.

Prêter une String

Si tu veux prêter une chaîne à une fonction sans en transférer la propriété, utilise une référence (&). Cela te permet d’accéder à la chaîne sans en prendre possession.

fn main() {
let s = String::from("Rust");
emprunter(&s); // `s` est empruntée
println!("{}", s); // `s` est toujours accessible
}
fn emprunter(s: &String) {
println!("{}", s);
}

Ici, la fonction emprunter() prend une référence à la chaîne, donc s reste valide après l’appel de la fonction.


Slices UTF-8 et caractères spéciaux

En Rust, les chaînes sont encodées en UTF-8, ce qui signifie qu’elles peuvent contenir des caractères multi-octets. Cela rend Rust très puissant pour manipuler des chaînes avec des caractères spéciaux ou des langues non latines.

Toutefois, comme les caractères peuvent occuper plusieurs octets, certaines opérations comme l’indexation directe peuvent être délicates.

let s = String::from("नमस्ते"); // En hindi
let slice = &s[0..3]; // Prend les trois premiers octets
println!("{}", slice); // Affiche un caractère incomplet

Dans cet exemple, नमस्ते est encodé en plusieurs octets. Une slice partielle de la chaîne peut entraîner une erreur si elle ne tombe pas exactement sur une limite de caractère UTF-8.


Conclusion

Les chaînes en Rust, qu’elles soient de type String ou &str nécessitent de bien comprendre les concepts de propriété et de référence pour être utilisées correctement.

Avec cette compréhension, tu pourras gérer efficacement les strings dans tes programmes tout en profitant des avantages de la sécurité mémoire que Rust garantit.