August 6, 20253 min

String vs. &str – Lequel Utiliser et Quand ?

m
mayo

Comprendre la différence entre String et str est fondamental pour une gestion efficace de la mémoire.

Différences Clés

String str (habituellement &str)
String UTF-8 extensible, heap-allocated Vue immutable, taille fixe dans string UTF-8
Type owned (gère sa mémoire) Type borrowed (ne possède pas la mémoire)
Mutable (peut modifier le contenu) Vue immutable
Créé avec String::from("...") ou "...".to_string() Depuis string literals ("hello") ou emprunté depuis String (&my_string)

Layout Mémoire

String : Stocke les données sur la heap avec trois composants :

  • Pointeur vers buffer heap
  • Length (taille actuelle)
  • Capacity (taille allouée)

&str : Un "fat pointer" contenant :

  • Pointeur vers données string (heap, stack, ou mémoire static)
  • Length de la slice

Cas d'usages

Utilise String quand :

  • Tu as besoin de modifier ou faire grandir la string
  • Tu as besoin d'ownership (ex : retourner depuis une fonction)
  • Construire des strings dynamiquement
let mut owned = String::from("hello");
owned.push_str(" world");  // Mutation nécessite String

Utilise &str quand :

  • Tu n'as besoin que d'une vue read-only d'une string
  • Travailler avec des paramètres de fonction (évite les allocations inutiles)
  • Gérer des string literals (stockées en mémoire read-only)
fn process_str(s: &str) -> usize {
    s.len()  // Accès read-only
}

Exemple : Ownership vs Borrowing

fn process_string(s: String) { /* prend ownership */ }
fn process_str(s: &str)      { /* emprunte */ }

fn main() {
    let heap_str = String::from("hello");
    let static_str = "world";
    
    process_string(heap_str);  // Ownership moved
    process_str(static_str);   // Borrowed
    
    // heap_str plus accessible ici
    // static_str encore accessible
}

Considérations de Performance

Paramètres de Fonction :

// Inefficace - force l'allocation
fn bad(s: String) -> usize { s.len() }

// Efficace - accepte String et &str
fn good(s: &str) -> usize { s.len() }

// Exemple:
let owned = String::from("test");
good(&owned);     // Deref coercion: String -> &str
good("literal");  // &str direct

Allocation Mémoire :

  • String alloue sur heap, nécessite désallocation
  • &str vers literals pointe vers le binaire du programme (zero allocation)
  • &str depuis String partage l'allocation existante

Patterns Courants

Renvoyer des "Owned" :

fn build_message(name: &str) -> String {
    format!("Hello, {}!", name)  // Retourne String owned
}

Paramètre d'entrée flexible :

fn analyze(text: &str) -> usize {
    // Fonctionne avec inputs String et &str
    text.chars().count()
}

Éviter les Clones Inutiles :

// Mauvais - allocation inutile
fn process_bad(s: &str) -> String {
    s.to_string()  // Seulement si tu as vraiment besoin de données owned
}

// Bon - travaille avec données borrowed quand possible
fn process_good(s: &str) -> &str {
    s.trim()  // Retourne slice de l'original
}

Points Clés

  • String : Owned, mutable, heap-allocated
  • str : Borrowed, immutable, flexible (heap/stack/static)
    🚀 Préfère &str pour les paramètres de fonction sauf si tu as besoin d'ownership ou mutation

Essaie Ceci : Que se passe-t-il quand tu appelles .to_string() sur un string literal vs un String ?

Réponse : Literal crée une nouvelle allocation heap ; String crée un clone des données de la heap existante. Donc les deux allouentde la mémoire, mais la source diffère !

Retour au blog
Partager ::