Rust : l'héritage n'existe pas !
Tags: rust, heritage, matrix
Published: 2022-10-21 08:32:13

# Rust : l'héritage n'existe pas

Après avoir vu le dernier opus de la saga _The Matrix_, j'ai été pris d'une envie de revenir aux sources. Alors, ici, il ne sera pas question de critique de film, mais de revenir sur un sujet technique essentiel qu'est l'héritage.

Tenez, Rust par exemple. Un langage natif, certes, mais moderne ([article découverte ici](https://www.younup.fr/blog/rust-ce-langage-natif-safe-et-puissant)). Eh bien ! il faut faire éclater une vérité, Néo : en Rust, l'héritage n'existe pas !


![la cuillère n'existe pas](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c3p5gaz3guadohrilz7b.jpg)


Alors dans ce cas, comment transmettre des fonctions ou des propriétés qui sont communes à de multiples structures de données, ou encore imposer un pattern de fonctionnalités ? Bref, ce qu'on a toujours fait avec les classes abstraites en C++, par exemple ?

Si vous aussi vous vous êtes déjà posé ces questions, alors suivez le lapin blanc...

Note : tous les extraits de code ci-après sont testables sur le [Playground Rust](https://play.rust-lang.org/).

## Pas d'héritage : pourquoi ?

Soyons clair, l'héritage est un concept essentiel de la programmation orientée objet (OOP en anglais), ce que Rust n'est pas.

Rust est un **langage C-like** où les "classes" sont avant tout des structures de données, et pas nécessairement des fonctionnalités. C'est aussi un langage qui se veut **explicite**. En C++, à travers plusieurs héritages successifs, on a tendance à perdre cet aspect.

En Rust, on est incité à coder des **petits modules indépendants et génériques**.

Bien entendu, Rust n'est pas en reste (haha) pour autant. Il a été pourvu d'un minimum vital pour gérer un certain niveau d'abstraction. Il y a principalement 2 solutions qui s'offrent à vous : les traits et les compositions.


![Dilemne des pilules](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t1yv22ukdc56nuc9v5te.jpg)



## Pilule rouge : les traits

Un `trait` est l'équivalence d'une interface en Java : on impose l'implémentation d'une liste de fonctions.

Prenons l'exemple des 3 grands agents du film : Smith, Jones et Brown (si vous ne connaissiez pas les 2 derniers noms, c'est cadeau !). Ils font initialement tous partie d'une même faction : les agents. Un agent, quel qu'il soit, doit être en mesure de repérer et d'éliminer les anomalies de la Matrice.

Si on devait les modéliser avec des `struct` on pourrait donc imposer la compétence `eliminer_anomalies` de cette manière :

```rust
// Trait générique
trait Agent {
    fn eliminer_anomalies(&self);
}

// Différentes "classes"
struct Smith {}
struct Jones {}
struct Brown {}

// Implémentations des traits
impl Agent for Smith {
    fn eliminer_anomalies(&self){
        println!("Smith doit éliminer anomalies")
    }
}

impl Agent for Jones {
    fn eliminer_anomalies(&self){
        println!("Jones doit éliminer anomalies")
    }
}

impl Agent for Brown {
    fn eliminer_anomalies(&self){
        println!("Brown doit éliminer anomalies")
    }
}

fn main() {
    let agent_smith = Smith {};
    let agent_jones = Jones {};
    let agent_brown = Brown {};

    agent_smith.eliminer_anomalies(); // "Smith doit éliminer anomalies"
    agent_jones.eliminer_anomalies(); // "Jones doit éliminer anomalies"
    agent_brown.eliminer_anomalies(); // "Brown doit éliminer anomalies"
}
```

Voilà nos 3 agents opérationnels !


![Les 3 agents](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1yrfvrqzr1d5em8xjeu9.jpg)


Pour information, on peut voir les traits en Rust un peu comme des traits de caractère d'un personnage : une structure peut également implémenter autant de traits que nécessaire.

## Pilule bleue : les compositions

Autre solution à préférer si vous avez besoin de transmettre des propriétés (et des fonctions par la même occasion) : composer les structures entre elles. C'est ce qui se rapproche le plus d'un "héritage" de propriétés.

Plaçons nous cette fois-ci du côté des êtres humains. Ils ont chacun un nom et peuvent évoluer au sein de la Matrice. Puis il y a les humains libérés qui en plus ont la capacité de sortir de la Matrice comme bon leur semble. Et pour finir, il y a l'élu : il dispose de toutes les capacités précédentes, et en plus il sait voler (le veinard !).

On pourrait donc imaginer une modélisation de ces classes de personnes comme suit :

```rust
struct Humain {
    nom: String
}

impl Humain {
    fn evoluer_dans_matrice(&self){
        println!("Dans la matrice...")
    }
}

struct Libere {
    humain: Humain
}

impl Libere {
    fn sortir_de_la_matrice(&self){
        println!("Hors de la matrice...")
    }
}

struct Elu {
    libere: Libere,
}

impl Elu {
    // (constructeur pour gagner en lisibilité)
    fn new() -> Self {
        Elu {
            libere: Libere {
                humain: Humain { nom: String::from("Néo") }
            }
        }
    }

    fn voler(&self){
        println!("Vole dans les airs !")
    }
}

fn main() {
    let elu = Elu::new();

    println!("Nom de l'élu: {}", elu.libere.humain.nom); // "Nom de l'élu: Néo"
    elu.libere.sortir_de_la_matrice(); // "Hors de la matrice..."
    elu.libere.humain.evoluer_dans_matrice(); // "Dans la matrice..."
    elu.voler(); // "Vole dans les airs !"
}
```

Ainsi, Néo hérite bien de toutes les compétences et propriétés en tant qu'élu et humain libéré. Evidemment d'un point de vue modélisation UML, l'élu devrait être une instance unique, mais ce n'est pas le sujet ici.


![Néo qui vole](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t7w0dl9t9a85oponshtf.gif)


Petit inconvénient : la composition n'impose pas l'implémentation de fonctions. Pour ça, il faut compléter avec les `trait`s.

Vous l'aurez compris, il y a un autre inconvénient bien plus visible : le code est bien explicite, mais il faut traverser toute la hiérarchie manuellement pour accéder aux propriétés les plus hautes. Pas toujours folichon.

En réalité, il existe une astuce lorsque la profondeur de données devient trop grande...

## Ptit verre d'eau pour faire passer : Deref

Le trait [std::ops::Deref](https://doc.rust-lang.org/std/ops/trait.Deref.html) sert à la base à surcharger le déréférencement des pointeurs, et il est conseillé de plutôt les utiliser sur les _smart pointers_.

Mais ici nous allons l'utiliser pour simplifier l'accès à nos variables, en ajoutant :

```rust
use std::ops::Deref;

// ...

impl Deref for Libere {
    type Target = Humain;
    fn deref(&self) -> &Humain {
        &self.humain
    }
}

impl Deref for Elu {
    type Target = Libere;
    fn deref(&self) -> &Libere {
        &self.libere
    }
}
```

Nous pouvons alors changer notre main :

```rust
fn main() {
    println!("Nom de l'élu: {}", elu.nom); // "Nom de l'élu: Néo"
    elu.sortir_de_la_matrice(); // "Hors de la matrice..."
    elu.evoluer_dans_matrice(); // "Dans la matrice..."
    elu.voler(); // "Vole dans les airs !"
}
```

Et hop, un accès direct !


![Néo met ses lunettes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4dbkazla2pc12whaqwnk.gif)


Si besoin, il faudra aussi implémenter le trait [std::ops::DerefMut](https://doc.rust-lang.org/std/ops/trait.DerefMut.html) pour obtenir un accès mutable à ces éléments. Rappelez vous : par défaut, tout est immuable en Rust.

Toutefois, retenez bien que c'est plus un hack utilitaire. À utiliser avec modération donc.

## Conclusion

L'héritage en Rust est un sujet qui revient régulièrement dans les discussions et les demandes d'évolution du langage. Mais bien que Rust ne soit pas un langage fondamentalement orienté objet, il dispose tout de même de fonctionnalités suffisantes pour gérer les abstractions. C'est aussi un coup à prendre ! Une autre façon de coder.

En résumé, privilégiez une déclaration explicite avec des `trait`s pour des fonctions virtuelles, et passez plutôt par des compositions de `struct` pour transmettre des propriétés.


![La matrice qui défile](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wxlazeexii9yn2pxfswz.gif)


_One More thing_

Pour votre curiosité, il existe un `crate` (comprenez module) intitulé `inheritance` en [version alpha](https://docs.rs/inheritance/0.0.1-alpha.2/inheritance/index.html). Il propose des macros pour implémenter automatiquement des traits sans avoir besoin de les réécrire dans toute la hiérarchie. Mais qui est seulement resté au stade expérimental...