II. Spécificités de Rust
14. Box
Le type Box est "tout simplement" un pointeur sur des données stockées "sur le tas" (la "heap" donc).
On s'en sert notamment quand on veut éviter de trop surcharger la pile (la "stack") en instanciant directement "sur le tas".
Ou encore pour avoir une adresse constante quand on utilise une FFI (Foreign Function Interface), comme des pointeurs sur objet/fonction. Nous reviendrons sur ce sujet dans la troisième partie du cours.
Pour rappel, un programme a accès a deux types de mémoires : le tas et la pile. La pile est utilisée quand on appelle une fonction ou que l'on crée une variable. Le tas est utilisé quand vous allouez de la mémoire vous-même. Si vous souhaitez donc que de la mémoire survive au scope de sa fonction, il vous faudra donc utilisée le tas.
Pour mieux illustrer ce qu'est le type Box, je vous propose deux exemples :
Structure récursive
On s'en sert aussi dans le cas où on ignore quelle taille fera le type, comme les types récursifs par exemple :
Run#[derive(Debug)]
enum List<T> {
Element(T, List<T>),
Vide,
}
fn main() {
let list: List<i32> = List::Element(1, List::Element(2, List::Vide));
println!("{:?}", list);
}
Si vous essayez de compiler ce code, vous obtiendrez une magnifique erreur : "invalid recursive enum type". (Notez que le problème sera le même si on utilise une structure). Ce type n'a pas de taille définie, nous obligeant à utiliser un autre type qui lui en a une (donc &
ou bien Box) :
Run#[derive(Debug)]
enum List<T> {
Element(T, Box<List<T>>),
Vide,
}
fn main() {
let list: List<i32> = List::Element(
1,
Box::new(List::Element(2, Box::new(List::Vide))),
);
println!("{:?}", list);
}
Liste chaînée
Box est également utile pour la création de listes chaînées (même s'il vaut mieux utiliser le type Vec à la place quasiment tout le temps) :
Runuse std::fmt::Display;
struct List<T> {
a: T,
// "None" signifiera qu'on est à la fin de la liste chaînée.
next: Option<Box<List<T>>>,
}
impl<T> List<T> {
pub fn new(a: T) -> List<T> {
List {
a: a,
next: None,
}
}
pub fn add_next(&mut self, a: T) {
match self.next {
Some(ref mut n) => n.add_next(a),
None => {
self.next = Some(Box::new(List::new(a)));
}
}
}
}
impl<T: Display> List<T> {
pub fn display_all_list(&self) {
println!("-> {}", self.a);
match self.next {
Some(ref n) => n.display_all_list(),
None => {}
}
}
}
fn main() {
let mut a = List::new(0u32);
a.add_next(1u32);
a.add_next(2u32);
a.display_all_list();
}
Voilà pour ce petit chapitre rapide. Box est un type important auquel les gens ne pensent pas forcément alors qu'il pourrait résoudre leur(s) problème(s). Il me semblait donc important de vous le présenter.