Rust

Any donation is very welcome
Fork me on GitHub

II. Spécificités de Rust

8. Sized et String vs str

Ce chapitre approfondi ce dont je vous ai déjà parlé dans un chapitre précédent, à savoir : la différence entre String et str. Ou encore : "Pourquoi deux types pour représenter la même chose ?". Tâchons d'y répondre !

str

Le type str représente tout simplement en mémoire une adresse et une taille. C'est pourquoi on ne peut modifier son contenu. Mais ce n'est pas la seule chose à savoir à son sujet. Commençons par regarder le code suivant :

Runlet x = "str";

x est donc une variable de type &str. Mais que se passe-t-il si nous tentons de déréférencer x pour obtenir un type str ?

Runlet x = *"str";

Ce qui donnera :

error: the trait `core::marker::Sized` is not implemented for the type `str` [E0277]

Mais quel est donc ce trait Sized, et pourquoi ça pose un problème que str ne l'implémente pas ?

Le trait Sized

str n'est pas le seul type qui n'implémente pas le trait Sized. Les slice non plus ne l'implémentent pas :

Runlet x: [i32] = [0, 1, 2];

Ce qui donne :

error: mismatched types:
expected `[i32]`,
   found `[i32; 3]`
# ...
error: the trait `core::marker::Sized` is not implemented for the type `[i32]` [E0277]

Le problème est donc que si le trait Sized n'est pas implémenté sur le type, cela signifie que l'on ne peut pas connaître sa taille au moment de la compilation. Du coup nous sommes obligés de passer par d'autres types pour les manipuler. Dans le cas des str et des slice, on peut se contenter d'utiliser des références :

Runlet x: &[i32] = &[0, 1, 2];
let x = "str";

Maintenant revenons-en aux String et aux str.

String

Les String permettent donc de manipuler des chaînes de caractères. En plus de ce que contient str (à savoir : une adresse mémoire et une longueur), elles contiennent aussi une capacité qui représente la quantité de mémoire réservée (mais pas nécessairement utilisée).

Pour résumer un peu le tout, String est une structure permettant de modifier le contenu d'une "vue" constante représentée par le type str. C'est d'ailleurs pour ça qu'il est très simple de passer de l'un à l'autre :

Runlet x: &str = "a";
let y: String = x.to_owned(); // on aurait aussi pu utiliser String::from
let z: &str = &y;

Vec vs slice

C'est plus ou moins le même fonctionnement : le type Vec permet de modifier le contenu d'une vue (non constante) représentée par les slice. Exemple :

Runlet x: &[i32] = &[0, 1, 2];
let y: Vec<i32> = x.to_vec();
let z: &[i32] = &y;

Ce chapitre (et notamment le trait Sized) est particulièrement important pour bien comprendre les mécanismes sous-jacents de Rust. N'hésitez pas à le relire plusieurs pour être bien sûr d'avoir tout compris avant de passer à la suite !