Aller au contenu

Utiliser Les modules en Rust

Rust fournit un système de modules puissant qui peut être utilisé pour diviser le code en unités logiques (modules) et gérer leur visibilité (publique/privée) entre elles.

Ça nous permet d’organiser notre code proprement et de nous y retrouver plus facilement.

Un module est une collection d’éléments : fonctions, structures, traits, blocs impl et même d’autres modules.

Premier Module

Pour créer un module, il suffit d’utiliser le mot clé mod suivi du nom du module puis des accolades, tout ce qui trouvera à l’intérieur appartiendra au module.

mod demo {
fn say_hello(){
println!("Hellllooooo");
}
}
fn main(){
demo::say_hello(); // ERREUR
}

Donc ici on a un module demo qui contient une fonction say_hello.

Le soucis ici c’est que si on lance le code on a une erreur qui nous dit que la fonction est privé…

La visibilité

Par défaut tout ce qui est dans un module a une visibilité privé, c’est à dire que tout ce qui est dans le module ne pourra pas être appelé depuis l’extérieur.

Encore une fois on reconnaît ici le comportement ultra safe de Rust qui veut forcer le développeur à définir explicitement ce qui doit être accessible depuis l’extérieur pour éviter d’exposer des items qui ne devraient pas l’être.

Pour régler ce problème il suffit d’ajouter le mot clé pub devant l’item qu’on veut utiliser depuis l’extérieur.

mod demo{
pub fn say_hello(){
println!("Hellllooooo");
}
}
fn main(){
demo::say_hello(); // Hellllooooo
}

Les modules peuvent eux aussi contenir des modules, et encore une fois si on veut les rendre accessible il faudra ajouter le mot clé pub devant.

mod demo{
pub fn say_hello(){
println!("Hellllooooo");
}
// Là aussi on met pub
pub mod foo{
// Là encore
pub fn say_ciao(){
println!("CIAOOOO");
}
}
}
fn main(){
demo::say_hello(); // Hellllooooo
demo::foo::say_ciao();// CIAOOOO
}

Bref tu l’as compris, tout ce qui doit être appeler depuis l’extérieur du module doit être précédé du mot clé pub

La visibilité des structs

Pour les structs à l’intérieur d’un module on a un peu plus de travail…

Premièrement le struct lui même doit être accessible depuis l’extérieur, donc avoir le mot clé pub

Ensuite il va falloir préciser quel champ est accessible toujours avec le mot clé pub.

mod demo{
pub fn say_hello(){
println!("Hellllooooo");
}
pub struct User{
pub name: String, // accessible depuis l'exterieur
password: String // Pas accessible depuis l'exterieur
}
}

Là tu dois te dire : “Bon okay, mais si je peux pas accéder à password, comment je fais pour instancier mon struct User ?!”

Pour cela on va utiliser une fonction associé qu’on va appeler new et qui va générer le password automatiquement

mod demo{
pub fn say_hello(){
println!("Hellllooooo");
}
pub struct User{
pub name: String, // accessible depuis l'exterieur
password: String // Pas accessible depuis l'exterieur
}
impl User {
// new doit être pub pour être accessible
pub fn new(name:String) -> User {
let password = String::from("A random String");
User{name:name, password:password}
}
}
}
fn main(){
let user = demo::User::new(String::from("jojo"));
}

Et voilà ! De cette façon password n’est pas accessible depuis l’extérieur MAIS on peut quand même générer une instance de User.

self et super

Le mot clé self peut être utilisé dans un module pour indiquer qu’on utilise un item du même module, à noter que c’est optionnel car par défaut le module va chercher les items à l’intérieur de lui même.

mod demo{
pub fn say_hello(){
self::say_yo(); // On appelle say_yo
say_yo(); // Exactement la même chose qu'au dessus
println!("Hellllooooo");
}
pub fn say_yo(){
println!("YO!");
}
}

Le mot clé super permet d’appeler un item à dans le scope parent au module en question.

pub fn say_yo(){
println!("YO!");
}
mod demo{
pub fn say_hello(){
super::say_yo();
println!("Hellllooooo");
}
}

Importer d’autres fichiers

On va pouvoir importer d’autres fichiers très simplement.

Imaginons qu’on crée un fichier jack.rs dans notre dossier src.

Fenêtre de terminal
src
├── jack.rs
└── main.rs

On va maintenant ajouter une fonction say_ciao dans notre fichier jack.rs et on va la rendre public pour pouvoir s’en servir dans le fichier main.rs

jack.rs
pub fn say_ciao(){
println!("CIAOOO");
}

Et maintenant pour l’utiliser dans main.rs il suffit d’ajouter en haut du fichier mod jack;, cela va indiquer à Rust d’utiliser le fichier en question.

main.rs
mod jack;
fn main() {
jack::say_ciao();
}

Et voilà on peut maintenant utiliser le fichier jack.rs depuis notre fichier main.rs!

Quand on utilise le mot clé mod sans accolade comme dans mod jack; Rust va automatiquement :

  • Chercher un fichier nommé jack.rs au même niveau que le fichier dans lequel on se trouve (ici au même niveau que main.rs) et l’importer afin de le rendre accessible.

  • Si il ne le trouve pas il va chercher un dossier nommé jack et va alors chercher un fichier nommé mod.rs à l’intérieur et l’importer afin de le rendre accessible, pourquoi il va chercher un fichier mod.rs ? C’est une convetion.

Ce fichier mod.rs va définir tous les modules utilisé par notre module actuel.

Pour illustrer ça on va créer une fichier bernie.rs dans notre dossier jack.

Fenêtre de terminal
src
├── jack
├── bernie.rs
└── mod.rs
└── main.rs

Le fichier bernie va contenir la fonction qu’on veut importer :

bernie.rs
pub fn say_ciao(){
println!("CIAOOO");
}

Ensuite le fichier mod.rs va définir les modules qu’utilise notre module jack.

Encore une fois on doit définir les modules en public si on veut les appeler depuis l’extérieur, ici le module qu’utilise jack est bernie.

mod.rs
pub mod bernie;

Et enfin dans main.rs

main.rs
mod jack;
fn main() {
jack::bernie::say_ciao();
}

Le mot clé use

Concrètement c’est le mot clé qu’on utilise pour importer des items de librairies externes.

use std::io::{stdout, BufWriter};
fn main() {
let stdout = stdout();
}

On peut maintenant utiliser stdout et BufWriter directement.

Le Glob opérator *

Pour importer tous les items d’un namespace dans un scope on utilise la syntaxe * qui est appelé le global opérator.

use chrono::prelude::*;
fn main() {
let local: DateTime<Local> = Local::now();
}

On peut maintenant utiliser tout ce qui est contenu dans chrono::prelude directement sans être précédé de quoi que ce soit.