Variables & fonctions

Variables & fonctions

Variables, Mutabilité et Fonctions

Guide Complet pour Débutants

1. Les variables en Rust

En Rust, les variables sont déclarées avec le mot-clé let . Une des particularités de Rust est que les variables sont immutables par défaut .

1.1 - Déclaration de base

// Déclaration simple
let x = 5;
println!("La valeur de x est : {}", x);
// Déclaration avec type explicite
let y: i32 = 10;
println!("La valeur de y est : {}", y);
// Déclaration de plusieurs variables
let a = 1;
let b = 2;
let c = 3;

I Note : En Rust, on utilise println! (avec un !) pour afficher du texte. Le {} est remplacé par la valeur de la variable.

1.2 - Immutabilité par défaut

Par défaut, les variables Rust sont immutables : on ne peut pas changer leur valeur après leur déclaration.

let x = 5;
println!("x = {}", x);

x = 6; // I ERREUR DE COMPILATION ! // error[E0384]: cannot assign twice to immutable variable x``

II Pourquoi ? L'immutabilité par défaut aide à éviter les bugs. Si tu veux modifier une variable, tu dois le déclarer explicitement avec mut .

1.3 - Variables mutables

Pour rendre une variable modifiable, on ajoute mut après let .

let mut x = 5; println!("x = {}", x); // Affiche : x = 5 x = 6; // I OK ! println!("x = {}", x); // Affiche : x = 6

// Modification multiple
let mut compteur = 0;
compteur = compteur + 1;
compteur = compteur + 1;
println!("compteur = {}", compteur);  // Affiche : compteur = 2

I Conseil : Utilise mut seulement quand nécessaire. Cela rend ton code plus sûr et plus facile à comprendre.

1.4 - Les constantes

Les constantes sont déclarées avec const et sont toujours immutables . Elles doivent avoir un type explicite et sont évaluées à la compilation.

// Constante (MAJUSCULES par convention)
const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.14159;
fn main() {
    println!("Le maximum est : {}", MAX_POINTS);

// I Impossible avec une constante :

  • // const ne peut pas être mut
    // const doit avoir un type explicite
}

Différences entre let et const :

letconst
Immutable par défautII
Peut être mutII
Type explicite requisII
PortéeBlockGlobale
Valeur calculéeRuntimeCompilation
Convention nomsnake_caseUPPER_CASE

1.5 - Le shadowing

Le shadowing permet de redéclarer une variable avec le même nom. C'est différent de la mutabilité !

let x = 5; println!("x = {}", x); // 5 let x = x + 1; // I Nouvelle variable qui masque la précédente println!("x = {}", x); // 6 let x = x * 2; // I Encore une nouvelle variable println!("x = {}", x); // 12 // Shadowing permet de changer le type ! let espaces = " "; // Type: &str let espaces = espaces.len(); // Type: usize println!("Il y a {} espaces", espaces); // 3

Shadowing vs mut :

mut : Modifie la valeur, même type

  • let (shadowing) : Crée une nouvelle variable, peut changer de type

2. Les types de données

Rust est un langage statiquement typé : chaque variable a un type connu à la compilation. Rust peut souvent inférer le type, mais tu peux aussi l'annoter explicitement.

2.1 - Types scalaires

Les types scalaires représentent une valeur unique. Rust en a quatre types principaux :

// 1. ENTIERS (integers)
let a: i32 = 42;        // Entier signé 32 bits
let b: u64 = 100;       // Entier non signé 64 bits
let c = 5;              // i32 par défaut
// Différentes tailles : i8, i16, i32, i64, i128, isize
//                       u8, u16, u32, u64, u128, usize
// 2. FLOTTANTS (floating-point)
let x: f64 = 2.5;       // 64 bits (défaut)
let y: f32 = 3.14;      // 32 bits
// 3. BOOLÉENS (boolean)
let vrai: bool = true;
let faux: bool = false;

// 4. CARACTÈRES (char) let lettre: char = 'A'; let emoji: char = ' I '; // Unicode !

Types d'entiers en Rust :

TypeTailleMinimumMaximum
i88 bits-128127
u88 bits0255
i3232 bits-2 milliards2 milliards
u3232 bits04 milliards
i6464 bitsTrès grand négatifTrès grand positif
isizeTaille du systèmeVariableVariable

2.2 - Types composés

Les types composés regroupent plusieurs valeurs dans un seul type.

// 1. TUPLES - Types différents, taille fixe
let personne: (&str, i32, bool) = ("Alice", 25, true);
// Accès par déstructuration
let (nom, age, actif) = personne;
println!("{} a {} ans", nom, age);
// Accès par index
println!("Nom : {}", personne.0);
println!("Age : {}", personne.1);
// 2. TABLEAUX - Même type, taille fixe
let nombres: [i32; 5] = [1, 2, 3, 4, 5];
// Accès par index
let premier = nombres[0];  // 1
let dernier = nombres[4];  // 5
// Tableau avec valeur répétée
let zeros = [0; 10];  // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
// 3. SLICES - Vue sur une partie d'un tableau
let slice = &nombres[1..3];  // [2, 3]

2.3 - Inférence de type

// Rust infère le type automatiquement
let x = 5;           // i32 (défaut pour entiers)
let y = 2.5;         // f64 (défaut pour flottants)
let z = true;        // bool
let nom = "Alice";   // &str
// Rust peut inférer selon l'utilisation
let mut nombres = Vec::new();  // Type inconnu
nombres.push(1);               // Maintenant Rust sait : Vec<i32>

2.4 - Annotation de type

// Parfois nécessaire pour lever l'ambiguïté
let nombre: u32 = "42".parse().expect("Pas un nombre!");
// Types complexes
let vecteur: Vec<i32> = vec![1, 2, 3];
let hashmap: std::collections::HashMap<String, i32>
    = std::collections::HashMap::new();

3. Les fonctions

Les fonctions sont omniprésentes en Rust. Elles sont déclarées avec fn et utilisent la convention snake_case pour les noms.

3.1 - Déclaration de base

// Fonction sans paramètre ni retour fn dire_bonjour() { println!("Bonjour !"); } // Fonction principale fn main() { dire_bonjour(); // Appel de fonction dire_bonjour(); // On peut l'appeler plusieurs fois } // Convention de nommage fn ma_fonction() { } // I snake_case fn MaFonction() { } // I éviter fn calculer_total() { } // I fn calculerTotal() { } // I éviter (camelCase)

3.2 - Paramètres

Les paramètres doivent toujours avoir un type explicite .

// Un paramètre
fn afficher_nombre(x: i32) {
    println!("Le nombre est : {}", x);
}
// Plusieurs paramètres
fn additionner(a: i32, b: i32) {
    let somme = a + b;
    println!("{} + {} = {}", a, b, somme);
}
// Appel
fn main() {
    afficher_nombre(42);
    additionner(5, 7);
}
// Paramètres de types différents
fn presenter(nom: &str, age: i32, actif: bool) {
    println!("{} a {} ans, actif: {}", nom, age, actif);
}

3.3 - Valeur de retour

Une fonction retourne une valeur avec -> suivi du type. La dernière expression est retournée automatiquement (pas de return nécessaire).

// Fonction avec retour fn additionner(a: i32, b: i32) -> i32 { a + b // II PAS de point-virgule ! }

fn main() {
    let resultat = additionner(5, 3);
    println!("5 + 3 = {}", resultat);  // 8
}
// Avec return explicite (utile pour retour anticipé)
fn valeur_absolue(x: i32) -> i32 {
    if x < 0 {
        return -x;  // Retour anticipé
    }
    x  // Retour normal
}
// Plusieurs retours possibles
fn diviser(a: i32, b: i32) -> Option<i32> {
    if b == 0 {
        return None;  // Division par zéro
    }
    Some(a / b)
}

II Attention ! En Rust, une expression sans point-virgule est retournée. Avec point-virgule, c'est une instruction qui ne retourne rien.

3.4 - Expressions vs instructions

// I Expression (retourne une valeur) fn plus_un(x: i32) -> i32 { x + 1 // Pas de ;

}

// I Instruction (ne retourne rien) fn plus_un_incorrect(x: i32) -> i32 { x + 1; // Avec ; = ne retourne rien ! // error: mismatched types (expected i32, found ()) }

// Les blocs sont des expressions
fn exemple() -> i32 {
    let x = {
        let y = 3;
        y + 1  // Cette expression est la valeur du bloc
    };  // x vaut 4
    x * 2  // Retourne 8
}

3.5 - Fonctions avec références

Pour éviter de copier les données, on peut passer des références aux fonctions.

// Sans référence (copie la valeur)
fn afficher_nombre(x: i32) {
    println!("Nombre : {}", x);
}
// Avec référence (emprunte la valeur)
fn afficher_chaine(s: &String) {
    println!("Chaîne : {}", s);
}
// Référence mutable (peut modifier)
fn incrementer(x: &mut i32) {
    *x += 1;  // * = déréférencement
}
fn main() {
    let nombre = 42;
    afficher_nombre(nombre);  // Copie
    let texte = String::from("Hello");
    afficher_chaine(&texte);  // Emprunte
    let mut compteur = 0;
    incrementer(&mut compteur);
    println!("Compteur : {}", compteur);  // 1
}

4. Ownership et Borrowing (introduction)

L' ownership est le concept le plus important et unique de Rust. C'est ce qui permet à Rust d'être sûr sans garbage collector.

4.1 - Le concept d'ownership

Règles de base :

  1. Chaque valeur a un propriétaire unique
  1. Il ne peut y avoir qu' un seul propriétaire à la fois
  1. Quand le propriétaire sort du scope, la valeur est libérée

fn main() { let s1 = String::from("hello"); let s2 = s1; // s1 est "déplacé" vers s2 // println!("{}", s1); // I ERREUR ! s1 n'est plus valide println!("{}", s2); // I OK } // Exemple avec fonction fn prend_ownership(s: String) { println!("{}", s); } // s est libéré ici

fn main() {
    let texte = String::from("hello");
    prend_ownership(texte);

// println!("{}", texte); // I ERREUR ! // texte a été déplacé dans la fonction }

I Types Copy : Les types simples (i32, f64, bool, char) sont Copy : ils sont copiés au lieu d'être déplacés.

4.2 - Les références (&)

Les références permettent d'emprunter une valeur sans en prendre ownership.

fn calculer_longueur(s: &String) -> usize {
    s.len()
}  // s sort du scope, mais ne possède pas la String

fn main() { let texte = String::from("hello"); let longueur = calculer_longueur(&texte); println!("'{}' a {} caractères", texte, longueur); // I texte est toujours valide ! }

// Multiples références immutables OK fn main() { let s = String::from("hello"); let r1 = &s; let r2 = &s; println!("{} et {}", r1, r2); // I OK }

4.3 - Les références mutables (&mut;)

fn ajouter_monde(s: &mut String) {
    s.push_str(", world!");
}
fn main() {
    let mut texte = String::from("hello");
    ajouter_monde(&mut texte);
    println!("{}", texte);  // "hello, world!"
}

// II UNE SEULE référence mutable à la fois ! fn main() { let mut s = String::from("hello"); let r1 = &mut s; // let r2 = &mut s; // I ERREUR ! // On ne peut pas avoir deux références mutables println!("{}", r1); } // II Pas de mélange immutable + mutable fn main() { let mut s = String::from("hello"); let r1 = &s; // OK let r2 = &s; // OK // let r3 = &mut s; // I ERREUR ! println!("{} et {}", r1, r2); }

II Règles du borrowing : 1. Soit une référence mutable 2. Soit plusieurs références immutables 3. Mais jamais les deux en même temps !

5. Exercices pratiques

Voici quelques exercices pour pratiquer :

Exercice 1 : Variables

Complète ce code pour qu'il compile :

fn main() {
    let x = 5;
    // Ajoute le code nécessaire pour que x devienne 10
    println!("x = {}", x);  // Doit afficher "x = 10"
}
// Solution :
fn main() {
    let mut x = 5;  // Ajouter mut
    x = 10;
    println!("x = {}", x);
}

Exercice 2 : Fonction simple

Crée une fonction qui multiplie deux nombres :

// À compléter
fn multiplier(/* paramètres */) /* -> type */ {
    // ton code
}
fn main() {
    let resultat = multiplier(6, 7);
    println!("6 x 7 = {}", resultat);  // Doit afficher 42
}
// Solution :
fn multiplier(a: i32, b: i32) -> i32 {
    a * b
}

Exercice 3 : Références

Crée une fonction qui double un nombre sans le déplacer :

fn doubler(/* référence mutable */) {
    // ton code
}
fn main() {
    let mut nombre = 5;
    doubler(/* passer référence */);
    println!("Nombre : {}", nombre);  // Doit afficher 10
}
// Solution :
fn doubler(x: &mut i32) {
    *x *= 2;
}
fn main() {
    let mut nombre = 5;
    doubler(&mut nombre);
    println!("Nombre : {}", nombre);
}

Variables

SyntaxeDescriptionExemple
let x = 5;Variable immutablelet nom = "Alice";
let mut x = 5;Variable mutablelet mut compteur = 0;
const MAX: i32 = 100;Constanteconst PI: f64 = 3.14;
let x = 5; let x = 10;Shadowinglet x = x + 1;

Fonctions

SyntaxeDescriptionExemple
fn nom() { }Fonction simplefn dire_bonjour() { }
fn nom(x: i32) { }Avec paramètresfn afficher(n: i32) { }
fn nom() -> i32 { }Avec retourfn double(x: i32) -> i32 { x * 2 }
fn nom(s: &String) { }Référencefn longueur(s: &String) -> usize { }
fn nom(x: &mut i32) { }Réf. mutablefn incrementer(x: &mut i32) { }

Types courants

TypeDescriptionExemple
i32, u32, i64...Entierslet x: i32 = 42;
f32, f64Flottantslet pi: f64 = 3.14;
boolBooléenlet actif: bool = true;
charCaractèrelet lettre: char = 'A';
&strChaîne immutablelet texte = "hello";
StringChaîne mutablelet s = String::from("hi");

Bravo !

Tu connais maintenant les bases de Rust !

Les prochaines étapes :

• Pratiquer avec de petits programmes

• Apprendre les structures (struct) et enums

• Maîtriser le pattern matching

• Explorer les collections (Vec, HashMap)

I Continue à coder et n'aie pas peur des erreurs du compilateur : elles sont là pour t'aider ! I

Ressources recommandées :

I The Rust Book (en français) : https://jimskapt.github.io/rust-book-fr/

I Rustlings (exercices) : https://github.com/rust-lang/rustlings

I Rust by Example : https://doc.rust-lang.org/rust-by-example/

I Forum Rust : https://users.rust-lang.org/