Les 3
Variabelen, mutability en typen
Variabelen met let en let mut, waarom ze standaard onveranderlijk zijn, shadowing, constanten, en de scalaire typen van Rust: gehele getallen, kommagetallen, bool en char.
🎯 Na deze les kun je
- variabelen declareren met en zonder mut
- shadowing uitleggen en toepassen
- scalaire typen kiezen en expliciet omzetten met as
- overflow en typefouten herkennen
In de meeste talen kun je een variabele altijd aanpassen. In Rust niet: een variabele is standaard onveranderlijk. Dat lijkt onhandig, maar het maakt code voorspelbaar: als je let prijs = 10; ziet, weet je zeker dat prijs verderop nog steeds 10 is. Wil je wél aanpassen, dan zeg je dat expliciet.
let en let mut
fn main() {
let x = 5;
// x = 6; // fout: cannot assign twice to immutable variable
let mut teller = 0;
teller += 1;
teller += 1;
println!("{x} {teller}"); // 5 2
}
Probeer de regel met x = 6 en lees de foutmelding: de compiler stelt zelf voor om let mut x te schrijven.
Gebruik mut alleen waar het nodig is. Krijg je de waarschuwing variable does not need to be mutable, haal het dan weg.
Typen en type-inferentie
Elke waarde in Rust heeft een vast type, dat de compiler bij het compileren kent. Meestal leidt hij het zelf af (type-inferentie), maar je kunt het ook opschrijven:
fn main() {
let a = 42; // i32 (standaard voor gehele getallen)
let b: u8 = 200; // expliciet: 8 bits, zonder teken
let c = 3.14; // f64 (standaard voor kommagetallen)
let d: f32 = 2.5;
let e = true; // bool
let f = 'ß'; // char: één Unicode-teken, 4 bytes
let g = 1_000_000; // underscores voor leesbaarheid
println!("{a} {b} {c} {d} {e} {f} {g}");
}
Gehele getallen
| Type | Bereik |
|---|---|
i8 / u8 |
−128..127 / 0..255 |
i16 / u16 |
−32 768..32 767 / 0..65 535 |
i32 / u32 |
±2,1 miljard / 0..4,3 miljard |
i64 / u64 |
±9,2 × 10¹⁸ / 0..1,8 × 10¹⁹ |
isize / usize |
64 bits op een 64-bitscomputer |
i = met teken (signed), u = zonder (unsigned). usize gebruik je voor indexen en lengtes: vec.len() geeft een usize.
Overflow
Wat gebeurt er bij 255u8 + 1?
fn main() {
let x: u8 = 255;
let y = x.wrapping_add(1); // 0: bewust "rondlopen"
let z = x.checked_add(1); // None: geen geldige uitkomst
let s = x.saturating_add(1); // 255: blijft op het maximum
println!("{y} {z:?} {s}");
}
Met gewone + hangt het af van de bouw: in een debugbuild stopt het programma met een panic ("attempt to add with overflow"); in een releasebuild loopt het rond naar 0. Een stille fout wordt dus in elk geval tijdens het testen opgemerkt. Wil je bewust een bepaald gedrag, gebruik dan wrapping_, checked_ of saturating_. (z:? drukt een waarde af met debug-opmaak; meer over Option in les 9.)
Geen automatische omzettingen
fn main() {
let a: i32 = 10;
let b: f64 = 2.5;
// let c = a * b; // fout: cannot multiply `i32` by `f64`
let c = a as f64 * b; // 25.0
let d = 7 / 2; // 3: geheeltallige deling
let e = 7.0 / 2.0; // 3.5
let f = -7 % 3; // -1: rest heeft het teken van het deeltal
println!("{c} {d} {e} {f}");
}
Rust zet nooit stilletjes een type om. Met as doe je het expliciet. Let op: as kapt af zonder waarschuwing (300i32 as u8 is 44, 3.9 as i32 is 3). Voor veilige omzettingen zijn er try_from en try_into (les 11).
Shadowing
Je kunt een nieuwe variabele met dezelfde naam maken. De oude wordt dan "overschaduwd":
fn main() {
let invoer = "42";
let invoer: i32 = invoer.parse().unwrap();
let invoer = invoer * 2;
println!("{invoer}"); // 84
}
Dit is iets anders dan mut: elke let maakt een nieuwe variabele, die zelfs een ander type mag hebben. Handig om een waarde stap voor stap om te vormen zonder namen als invoer_tekst en invoer_getal. (.parse().unwrap() zet tekst om naar een getal; wat unwrap doet en waarom je het later vervangt, zie je in les 11.)
Constanten
const BTW_HOOG: f64 = 0.21;
const MAX_POGINGEN: u32 = 3;
fn main() {
println!("{BTW_HOOG} {MAX_POGINGEN}");
}
Een const heeft altijd een expliciet type, wordt in HOOFDLETTERS geschreven, mag buiten functies staan, en moet bij het compileren bekend zijn.
Een temperatuurconverter
fn main() {
let celsius = [-40.0, 0.0, 21.5, 100.0];
for c in celsius {
let f = c * 9.0 / 5.0 + 32.0;
println!("{c:>6.1} °C = {f:>6.1} °F");
}
}
-40.0 °C = -40.0 °F
0.0 °C = 32.0 °F
21.5 °C = 70.7 °F
100.0 °C = 212.0 °F
Merk op: 9.0 / 5.0, niet 9 / 5. Met gehele getallen geeft dat 1, en klopt de omrekening niet; met een kommagetal (c) en een geheel getal (9) compileert het niet eens.
Typische fouten
cannot assign twice to immutable variable:mutvergeten.mismatched types: geheel getal en kommagetal gemengd. Gebruik2.0ofas f64.- Overflow-panic in debug: kies een groter type of een
checked_-variant. asvoor onveilige omzettingen zonder het bereik te controleren.
Oefeningen
- Wat drukt dit af:
let x = 5; let x = x + 1; { let x = x * 2; println!("{x}"); } println!("{x}");? - Welk type kies je voor: (a) de leeftijd van een mens, (b) het aantal bytes in een bestand, (c) een temperatuur met één decimaal?
- Schrijf de omrekening de andere kant op: Fahrenheit naar Celsius, voor 0, 50 en 100 °F.
Modelantwoord
12en daarna6. Het binnenste blok maakt een eigenx(12) die alleen binnen het blok bestaat; daarna is de buitenstex(6) weer zichtbaar.- (a)
u8is genoeg (0–255);u16ofu32mag ook. (b)u64: bestanden kunnen groter dan 4 GB zijn. (c)f64, of een geheel getal in tienden van graden (i32). -
fn main() { for f in [0.0, 50.0, 100.0] { let c = (f - 32.0) * 5.0 / 9.0; println!("{f} °F = {c:.1} °C"); } }
Lab: temperatuurconverter
- Druk een tabel af van −20 tot 40 °C in stappen van 5, met Fahrenheit en Kelvin.
- Gebruik een
constvoor het absolute nulpunt (−273,15). - Geen onnodige
mut;cargo buildzonder waarschuwingen. beetje check rust-les03
Samenvatting
letis onveranderlijk;let mutmag veranderen.- Gehele getallen
i8…i64,u8…u64,usize; kommagetallenf32/f64;bool;char. - Geen automatische omzetting:
as, of veilig mettry_from. - Overflow geeft in debug een panic; kies bewust
wrapping_,checked_ofsaturating_. - Shadowing maakt een nieuwe variabele met dezelfde naam;
constvoor vaste waarden.
Volgende les: functies, en waarom bijna alles in Rust een expressie is.
Lab
Temperatuurconverter
💻 Dit lab doe je op je eigen computer met Docker. Start de lab-container met (geeft de opdracht een eigen startcommando, gebruik dan dat):
$ docker run -it --rm registry.wizg.it/webhosting/beetje-labs:rust-les3
Klaar? Controleer je werk met:
$ beetje check rust-les3