ProgrammationDéveloppeur Backend

Quelles sont les particularités de travail avec des chaînes constantes et des chaînes dynamiques (String, &str) en Rust ? Quelles difficultés rencontrons-nous lors de leur utilisation et conversion ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Historique de la question

En Rust, on distingue deux types de chaînes principaux : &str (slice de chaîne, immuable, souvent littéral de chaîne) et String (chaîne dynamique, mutable). Aux premières étapes de développement du langage, le choix entre eux a permis de simplifier la gestion efficace de la mémoire et de garantir la sécurité des types lors du traitement des données textuelles grâce à un système strict de possession et de références.

Problème

De nombreux développeurs se perdent lors de l'interaction entre ces types. Par exemple, un littéral de chaîne est un &'static str, c'est-à-dire une référence à une chaîne immuable allouée au moment de la compilation, tandis que String peut s'étendre dynamiquement et contenir des données obtenues à l'exécution. Des questions se posent sur la manière de convertir entre les types, d'utiliser correctement la possession et d'éviter des copies inutiles.

Solution

La conversion entre &str et String est transparente si l'on comprend les règles de possession de base :

  • On peut obtenir un slice d'un String via une référence (my_string.as_str()) ou un simple emprunt (&my_string).
  • On peut convertir &str en String en utilisant to_string() ou String::from().
  • La possession et la mutabilité déterminent s'il est possible de modifier la chaîne ou s'il faut la cloner.

Exemple de code :

fn main() { let s_literal: &str = "hello"; let s_string: String = String::from(s_literal); let s_slice: &str = &s_string; let new_string = s_slice.to_string(); println!("{} {}", s_string, new_string); }

Caractéristiques clés :

  • &str n'occupe pas de mémoire sur le tas, toujours immuable.
  • String alloue de la mémoire dynamiquement, peut être modifiée.
  • Conversion simple avec une compréhension claire de la possession et des références.

Questions piégeuses.

Peut-on modifier un littéral de chaîne en Rust ?

Non, un littéral de chaîne (&'static str) est toujours immuable, toute tentative de modification d'un caractère entraînera une erreur à la compilation.

Est-il suffisant d'appeler .to_string() sur &str pour obtenir une chaîne mutable sans copies inutiles ?

Non, .to_string() alloue toujours une nouvelle mémoire et copie le contenu. C'est inévitable si une chaîne mutable est nécessaire à partir d'un slice.

Peut-on obtenir une référence &str à partir de String sans risque de fuite de durée de vie ?

Oui, une référence obtenue de cette manière (let s: &str = &my_string;) ne vit pas plus longtemps que le String d'origine. Essayer de retourner &str d'un String local dans une fonction entraînera une erreur de durée de vie.

Erreurs typiques et anti-patrons

  • Conserver une référence &str vers un String temporaire qui sort de la portée
  • Convertir chaque fois &str en String sans nécessité (allocations inutiles)
  • S'attendre à ce que String::from("text") ne crée pas de copie des données

Exemple de la vie réelle

Cas négatif

La fonction retourne &str, qui fait référence à un String temporaire à l'intérieur de la fonction :

fn faulty() -> &str { let s = String::from("Oops"); &s // erreur de durée de vie ! }

Avantages :

  • Semble simple

Inconvénients :

  • Ne compile pas, violant les règles de durée de vie
  • Risque de fuite de référence vers une mémoire déjà détruite

Cas positif

La fonction retourne directement un String et passe la possession à l'appelant :

fn correct() -> String { String::from("Safe!") }

Avantages :

  • Pas de problème de durée de vie
  • Code fiable

Inconvénients :

  • Peut être plus coûteux en mémoire que de passer une référence, si la chaîne est grande et qu'il n'est pas nécessaire de la posséder