1. Les types de base
Rust est statiquement typé : le type de chaque valeur est connu à la compilation. Il est souvent déduit, mais il existe toujours.
| Type | Contenu | Exemple |
|---|---|---|
i8 i16 i32 i64 i128 | Entiers signés (le nombre = nombre de bits) | let t: i32 = -12; |
u8 u16 u32 u64 u128 | Entiers non signés (jamais négatifs) | let age: u8 = 19; |
usize / isize | Taille de l’adresse machine : sert aux indices et aux longueurs | let i: usize = 3; |
f32 / f64 | Nombres décimaux (virgule flottante) | let pi = 3.14159; |
bool | true ou false | let admin = false; |
char | Un caractère Unicode, entre apostrophes | let c = 'é'; |
- Sans indication, un entier est un
i32et un décimal unf64. - Le séparateur
_rend les grands nombres lisibles :1_000_000. - Un
chars’écrit entre apostrophes, un texte entre guillemets :'a'n’est pas"a".
| Type | Minimum | Maximum |
|---|---|---|
u8 | 0 | 255 |
i8 | −128 | 127 |
u32 | 0 | 4 294 967 295 |
i32 | −2 147 483 648 | 2 147 483 647 |
u64 | 0 | environ 1,8 × 10¹⁹ |
2. Le dépassement d’entier
Que se passe-t-il si l’on dépasse le maximum d’un type ?
fn main() {
let niveau: u8 = 255;
let suivant = niveau + 1;
println!("{suivant}");
}
error: this arithmetic operation will overflow
--> src/main.rs:3:19
|
3 | let suivant = niveau + 1;
| ^^^^^^^^^^ attempt to compute `u8::MAX + 1_u8`, which would overflow
Ici la valeur est connue à l’avance : le compilateur refuse directement le programme. Quand elle ne l’est pas (une saisie, un calcul), le comportement dépend du mode de compilation.
| Mode | Comportement |
|---|---|
cargo run (debug) | Le programme s’arrête avec attempt to add with overflow |
cargo run --release | La valeur reboucle silencieusement : 255 + 1 donne 0 |
Quand un dépassement est possible, on dit explicitement ce que l’on veut :
fn main() {
let a: u8 = 250;
println!("{:?}", a.checked_add(10)); // None : le résultat ne tient pas
println!("{:?}", a.checked_add(3)); // Some(253)
println!("{}", a.saturating_add(10)); // 255 : bloqué au maximum
println!("{}", a.wrapping_add(10)); // 4 : rebouclage voulu
}
Some et None seront étudiés au niveau 3 : retiens pour l’instant que None signifie « pas de résultat valide ».
Convertir un nombre
fn main() {
let grand: i32 = 300;
let tronque = grand as u8; // 44 : conversion brutale, sans avertissement
println!("{tronque}");
let sur = u8::try_from(grand); // conversion vérifiée
println!("{:?}", sur); // Err(TryFromIntError(()))
let entier = 7;
let decimal = entier as f64 / 2.0; // 3.5 : int vers flottant, sans risque
println!("{decimal}");
}
3. Tuples et tableaux
fn main() {
// Tuple : des valeurs de types différents, en nombre fixe
let eleve = ("Lina", 19, 14.5);
println!("{} a {} ans et {} de moyenne", eleve.0, eleve.1, eleve.2);
let (nom, age, moyenne) = eleve; // déstructuration
println!("{nom} {age} {moyenne}");
// Tableau : des valeurs du même type, en nombre fixe
let notes = [12, 15, 9, 17, 11];
let zeros = [0; 10]; // dix zéros
println!("première : {}, nombre : {}", notes[0], notes.len());
println!("{:?}", zeros);
}
Le type d’un tableau contient sa taille : [i32; 5]. Pour des listes qui grandissent, on utilisera les vecteurs au niveau 4.
Si l’on demande une case qui n’existe pas avec un indice écrit en dur, le compilateur refuse le programme. Quand l’indice n’est connu qu’à l’exécution (une saisie, un calcul), le programme s’arrête :
fn note_numero(notes: &[i32], i: usize) -> i32 {
notes[i]
}
fn main() {
let notes = [12, 15, 9, 17, 11];
println!("{}", note_numero(¬es, 7));
}
thread 'main' panicked at src/main.rs:2:5:
index out of bounds: the len is 5 but the index is 7
4. Les fonctions
fn aire_rectangle(longueur: f64, largeur: f64) -> f64 {
longueur * largeur // pas de point-virgule : c'est la valeur renvoyée
}
fn afficher_titre(texte: &str) {
println!("=== {texte} ===");
}
fn main() {
afficher_titre("Calcul");
let a = aire_rectangle(12.5, 4.0);
println!("Aire : {a}");
}
- Le type de chaque paramètre est obligatoire.
- Le type de retour s’écrit après
->. Sans flèche, la fonction ne renvoie rien. &strdésigne un texte emprunté ; le niveau 2 explique le&.- L’ordre de déclaration des fonctions dans le fichier n’a pas d’importance.
Instructions et expressions
En Rust, presque tout est une expression qui produit une valeur. Un bloc { } vaut sa dernière expression, à condition qu’elle ne soit pas suivie d’un point-virgule.
fn carre(x: i32) -> i32 {
x * x // expression : renvoyée
}
fn carre_faux(x: i32) -> i32 {
x * x; // instruction : le bloc ne vaut rien → erreur E0308
}
return existe aussi, pour sortir avant la fin de la fonction : if x < 0 { return 0; }.
5. Décider : if et match
fn main() {
let note = 13;
if note >= 16 {
println!("Très bien");
} else if note >= 10 {
println!("Validé");
} else {
println!("À retravailler");
}
// if est une expression : il produit une valeur
let mention = if note >= 10 { "admis" } else { "ajourné" };
println!("{mention}");
}
- La condition n’a pas de parenthèses et doit être un
bool:if note { }est refusé, contrairement à Python ou C. - Opérateurs :
==,!=,<,>,<=,>=, et&&(et),||(ou),!(non). - Dans un
ifqui produit une valeur, toutes les branches doivent produire le même type.
match compare une valeur à une série de motifs. Il est exhaustif : tous les cas doivent être couverts, sinon le programme ne compile pas.
fn main() {
let note = 13;
let appreciation = match note {
0..=4 => "très insuffisant",
5..=9 => "insuffisant",
10..=13 => "correct",
14..=16 => "bien",
17..=20 => "excellent",
_ => "note invalide", // tout le reste
};
println!("{appreciation}");
let jour = 6;
match jour {
1 | 2 | 3 | 4 | 5 => println!("jour de cours"),
6 | 7 => println!("week-end"),
_ => println!("jour inconnu"),
}
}
6. Répéter : loop, while, for
fn main() {
// for sur un intervalle : 1 à 5 inclus
for i in 1..=5 {
print!("{i} ");
}
println!();
// for sur un tableau
let notes = [12, 15, 9];
for n in notes {
println!("note : {n}");
}
// à rebours, avec l'indice
for i in (1..4).rev() {
println!("{i}…");
}
// while
let mut capital = 1000.0;
let mut annees = 0;
while capital < 2000.0 {
capital *= 1.05;
annees += 1;
}
println!("Capital doublé en {annees} ans");
// loop : boucle infinie, dont on sort avec break (qui peut renvoyer une valeur)
let mut n = 1;
let premier_carre_sup_100 = loop {
if n * n > 100 {
break n * n;
}
n += 1;
};
println!("{premier_carre_sup_100}");
}
| Écriture | Valeurs parcourues |
|---|---|
0..5 | 0, 1, 2, 3, 4 (5 exclu) |
0..=5 | 0 à 5 inclus |
(0..5).rev() | 4, 3, 2, 1, 0 |
(0..10).step_by(2) | 0, 2, 4, 6, 8 |
continue passe au tour suivant, break sort de la boucle. Pour sortir de deux boucles imbriquées, on nomme la boucle extérieure : 'externe: for … puis break 'externe;.
7. Lire une saisie au clavier
use std::io;
fn main() {
println!("Quel âge as-tu ?");
let mut saisie = String::new();
io::stdin()
.read_line(&mut saisie)
.expect("lecture impossible");
let age: u32 = match saisie.trim().parse() {
Ok(valeur) => valeur,
Err(_) => {
println!("Ce n'est pas un nombre valide.");
return;
}
};
println!("Dans 10 ans, tu auras {} ans.", age + 10);
}
String::new()crée un texte vide et modifiable, dans lequelread_lineécrit la saisie.trim()retire le retour à la ligne final, sinon la conversion échoue.parse()tente la conversion : elle réussit (Ok) ou échoue (Err), et lematchtraite les deux cas.- Ce programme ne fonctionne pas dans le Playground, qui ne permet pas la saisie : utilise
cargo run.
8. Mini-projet : le juste prix
9. Bilan
Exercices corrigés
Les erreurs de ce niveau viennent presque toujours d’un mélange de types (entier et décimal, signé et non signé) ou d’un point-virgule en trop à la fin d’une fonction. Lis le message du compilateur avant de chercher ailleurs.
Exercice 1.1 — Types et limites
Voir le corrigé
fn main() {
// 1.
let x: u8 = 200;
println!("{:?}", x.checked_add(100)); // None : 300 ne tient pas dans un u8
println!("{}", x.saturating_add(100)); // 255 : bloqué au maximum
println!("{}", x.wrapping_add(100)); // 44 : 300 - 256
// 2.
println!("{} {} {}", i32::MAX, u64::MAX, i8::MIN);
// 3.
let moins_un: i32 = -1;
println!("{}", moins_un as u32); // 4294967295
println!("{:?}", u32::try_from(moins_un)); // Err(TryFromIntError(()))
// 4.
let c = 'é';
println!("{c} occupe {} octets", std::mem::size_of::<char>());
}
Explication
Le rebouclage calcule le résultat modulo 256 pour un u8 : 300 − 256 = 44. La conversion de −1 avec as réinterprète les bits : le résultat est le plus grand u32, une valeur absurde et silencieuse. Un char occupe 4 octets car il peut représenter n’importe quel caractère Unicode, emoji compris.
Exercice 1.2 — Fonctions et boucles
Voir le corrigé
fn est_pair(n: i32) -> bool {
n % 2 == 0
}
fn factorielle(n: u64) -> u64 {
let mut resultat = 1;
for i in 2..=n {
resultat *= i;
}
resultat
}
fn factorielle_sure(n: u64) -> Option<u64> {
let mut resultat: u64 = 1;
for i in 2..=n {
resultat = resultat.checked_mul(i)?; // ? : sort avec None en cas de dépassement
}
Some(resultat)
}
fn somme_chiffres(mut n: u32) -> u32 {
let mut somme = 0;
while n > 0 {
somme += n % 10;
n /= 10;
}
somme
}
fn main() {
for i in 1..=20 {
if est_pair(i) {
print!("{i} ");
}
}
println!();
println!("20! = {}", factorielle(20));
println!("20! = {:?}", factorielle_sure(20));
println!("21! = {:?}", factorielle_sure(21));
println!("{}", somme_chiffres(1234));
for i in 1..=30 {
match (i % 3, i % 5) {
(0, 0) => println!("FizzBuzz"),
(0, _) => println!("Fizz"),
(_, 0) => println!("Buzz"),
_ => println!("{i}"),
}
}
}
Explication
20! est la plus grande factorielle qui tient dans un u64. Le ? après checked_mul fait sortir la fonction avec None dès qu’une multiplication déborde : c’est une écriture que l’on retrouvera beaucoup au niveau 4. Le match sur un tuple teste deux conditions à la fois de manière très lisible ; _ signifie « n’importe quelle valeur ».
Exercice 1.3 — Saisie contrôlée
Voir le corrigé
use std::io;
fn main() {
let note: u32 = loop {
println!("Note sur 20 :");
let mut saisie = String::new();
io::stdin().read_line(&mut saisie).expect("lecture impossible");
let valeur: u32 = match saisie.trim().parse() {
Ok(v) => v,
Err(_) => {
println!("Ce n'est pas un nombre entier positif.");
continue;
}
};
if valeur > 20 {
println!("La note doit être entre 0 et 20.");
continue;
}
break valeur;
};
let appreciation = match note {
0..=4 => "très insuffisant",
5..=9 => "insuffisant",
10..=13 => "correct",
14..=16 => "bien",
_ => "excellent",
};
println!("{note}/20 : {appreciation}");
}
Explication
Le loop produit directement la valeur validée grâce à break valeur. Le contrôle se fait en deux temps : le type (u32 refuse le texte et les négatifs), puis les bornes métier (au plus 20). Le dernier cas du second match peut être _ car la note est garantie inférieure ou égale à 20.
Mini-projet
Voir le corrigé
use std::io;
use std::time::{SystemTime, UNIX_EPOCH};
const ESSAIS_MAX: u32 = 7;
fn nombre_secret() -> u32 {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("horloge système invalide")
.subsec_nanos();
nanos % 100 + 1
}
fn lire_proposition() -> Option<u32> {
let mut saisie = String::new();
io::stdin().read_line(&mut saisie).ok()?;
match saisie.trim().parse::<u32>() {
Ok(n) if (1..=100).contains(&n) => Some(n),
_ => None,
}
}
fn main() {
let secret = nombre_secret();
let mut essais = 0;
println!("J'ai choisi un nombre entre 1 et 100. Tu as {ESSAIS_MAX} essais.");
while essais < ESSAIS_MAX {
println!("Essai {} / {ESSAIS_MAX} :", essais + 1);
let proposition = match lire_proposition() {
Some(n) => n,
None => {
println!("Saisis un nombre entier entre 1 et 100.");
continue;
}
};
essais += 1;
if proposition < secret {
println!("Plus grand !");
} else if proposition > secret {
println!("Plus petit !");
} else {
println!("Gagné en {essais} essai(s) !");
return;
}
}
println!("Perdu ! Le nombre était {secret}.");
}
Explication
Le programme est découpé en fonctions qui ont chacune un rôle : produire le secret, lire une proposition valide, jouer. lire_proposition renvoie None pour toute saisie invalide ; la condition if (1..=100).contains(&n) placée dans le motif s’appelle une garde. Le compteur n’augmente qu’après une saisie valide.
Pour aller plus loin
Avec 7 essais et la stratégie « couper en deux », on trouve toujours un nombre entre 1 et 100 : 2⁷ = 128 > 100. C’est la recherche dichotomique, que tu retrouveras en algorithmique.
🧠 Testez vos connaissances
5 questions. Répondez puis cliquez sur « Corriger ». Rien n’est enregistré : c’est de l’auto-évaluation.
© 2026 Bruno Jousseaume. Ce cours est publié sous licence CC BY-NC-SA 4.0 : vous pouvez le partager et l’adapter en citant l’auteur et un lien vers cette page, sans usage commercial, et en partageant vos adaptations sous la même licence.