Variables & fonctions
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;
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 : impossible de changer leur valeur après leur déclaration.
let x = 5;
println!("x = {}", x);
x = 6; // ERREUR DE COMPILATION !
// error[E0384]: cannot assign twice to immutable variable `x`
Pourquoi ? L'immutabilité par défaut aide à éviter les bugs. Pour modifier une variable, il faut 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; // 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
Conseil : utiliser
mutseulement quand nécessaire — rend le 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);
// Impossible avec une constante :
// const ne peut pas être mut
// const doit avoir un type explicite
}
Différences entre let et const :
let | const | |
|---|---|---|
| Immutable par défaut | ✅ (sauf si mut) | ✅ (toujours) |
Peut être mut | ✅ Oui | ❌ Jamais |
| Type explicite requis | ❌ Non (inférence possible) | ✅ Oui, toujours |
| Portée | Bloc | Globale |
| Valeur calculée | À l'exécution | À la compilation |
| Convention de nom | snake_case | UPPER_CASE |
1.5 — Le shadowing
Le shadowing permet de redéclarer une variable avec le même nom — différent de la mutabilité.
let x = 5;
println!("x = {}", x); // 5
let x = x + 1; // Nouvelle variable qui masque la précédente
println!("x = {}", x); // 6
let x = x * 2; // Encore une nouvelle variable
println!("x = {}", x); // 12
// Le 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 obligatoire.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 on peut 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 = '🦀'; // Unicode !
Types d'entiers en Rust :
| Type | Taille | Minimum | Maximum |
|---|---|---|---|
i8 | 8 bits | -128 | 127 |
u8 | 8 bits | 0 | 255 |
i32 | 32 bits | -2 milliards | 2 milliards |
u32 | 32 bits | 0 | 4 milliards |
i64 | 64 bits | Très grand négatif | Très grand positif |
isize | Taille du système | Variable | Variable |
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 !");
}
fn main() {
dire_bonjour(); // Appel de fonction
dire_bonjour(); // On peut l'appeler plusieurs fois
}
// Convention de nommage
fn ma_fonction() { } // snake_case
fn MaFonction() { } // à éviter
fn calculer_total() { } // snake_case, correct
fn calculerTotal() { } // à é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 // 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)
}
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
// Expression (retourne une valeur)
fn plus_un(x: i32) -> i32 {
x + 1 // Pas de ;
}
// 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 ramasse-miettes (garbage collector).
4.1 — Le concept d'ownership
Règles de base :
- Chaque valeur a un propriétaire unique.
- Il ne peut y avoir qu'un seul propriétaire à la fois.
- 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); // ERREUR ! s1 n'est plus valide
println!("{}", s2); // 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); // ERREUR ! texte a été déplacé dans la fonction
}
Types
Copy: les types simples (i32,f64,bool,char) sontCopy— 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 l'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); // 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); // 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!"
}
// UNE SEULE référence mutable à la fois !
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
// let r2 = &mut s; // ERREUR ! On ne peut pas avoir deux références mutables
println!("{}", r1);
}
// 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; // ERREUR !
println!("{} et {}", r1, r2);
}
Règles du borrowing :
- Soit une référence mutable.
- Soit plusieurs références immutables.
- Mais jamais les deux en même temps.
5. Exercices pratiques
Exercice 1 — Variables
Compléter 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éer 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éer 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);
}
6. Aide-mémoire
Variables
| Syntaxe | Description | Exemple |
|---|---|---|
let x = 5; | Variable immutable | let nom = "Alice"; |
let mut x = 5; | Variable mutable | let mut compteur = 0; |
const MAX: i32 = 100; | Constante | const PI: f64 = 3.14; |
let x = 5; let x = 10; | Shadowing | let x = x + 1; |
Fonctions
| Syntaxe | Description | Exemple |
|---|---|---|
fn nom() { } | Fonction simple | fn dire_bonjour() { } |
fn nom(x: i32) { } | Avec paramètres | fn afficher(n: i32) { } |
fn nom() -> i32 { } | Avec retour | fn double(x: i32) -> i32 { x * 2 } |
fn nom(s: &String) { } | Référence | fn longueur(s: &String) -> usize { } |
fn nom(x: &mut i32) { } | Réf. mutable | fn incrementer(x: &mut i32) { } |
Types courants
| Type | Description | Exemple |
|---|---|---|
i32, u32, i64... | Entiers | let x: i32 = 42; |
f32, f64 | Flottants | let pi: f64 = 3.14; |
bool | Booléen | let actif: bool = true; |
char | Caractère | let lettre: char = 'A'; |
&str | Chaîne immutable | let texte = "hello"; |
String | Chaîne mutable | let s = String::from("hi"); |
Conclusion
Ce cours couvre les bases de Rust — variables, types, fonctions, ownership et borrowing.
Prochaines étapes :
- Pratiquer avec de petits programmes.
- Apprendre les structures (
struct) et les enums — voirstructures-enums.md. - Maîtriser le pattern matching — voir
structures-et-controle.md. - Explorer les collections (
Vec,HashMap) — voircollections.md.
Continue à coder et n'aie pas peur des erreurs du compilateur : elles sont là pour aider.
Ressources recommandées :
- The Rust Book (en français) : https://jimskapt.github.io/rust-book-fr/
- Rustlings (exercices) : https://github.com/rust-lang/rustlings
- Rust by Example : https://doc.rust-lang.org/rust-by-example/
- Forum Rust : https://users.rust-lang.org/