Les 4
Functies en control flow
Functies met parameter- en returntypen, het verschil tussen expressies en statements, if als expressie, en lussen met loop, while, for en ranges.
🎯 Na deze les kun je
- functies met typen schrijven
- het verschil tussen expressie en statement uitleggen
- if gebruiken als expressie
- lussen en ranges gebruiken, inclusief break met een waarde
Functies en lussen ken je uit andere talen. In Rust zien ze er bekend uit, met één belangrijk verschil dat overal terugkomt: bijna alles is een expressie, iets dat een waarde oplevert. Dat maakt veel code korter, en verklaart waarom een vergeten of juist overbodige puntkomma soms uitmaakt.
Functies
fn oppervlakte(breedte: f64, hoogte: f64) -> f64 {
breedte * hoogte
}
fn groet(naam: &str) {
println!("Hallo, {naam}!");
}
fn main() {
let a = oppervlakte(3.0, 4.5);
println!("{a}");
groet("Ali");
}
- Elke parameter heeft een verplicht type. De compiler leidt dat niet af, zodat de functie zelf duidelijke documentatie is.
-> f64is het returntype. Zonder->geeft de functie niets terug (eigenlijk(), het "lege" type).&stris een stukje tekst; in les 7 zie je precies wat dat is.- De volgorde van functies in het bestand maakt niet uit.
Expressies en statements
Kijk naar oppervlakte: er staat geen return, en de laatste regel heeft geen puntkomma. De laatste expressie van een blok is de waarde van het blok, en dus de returnwaarde.
- Een expressie levert een waarde:
breedte * hoogte,5,oppervlakte(1.0, 2.0), en ook een blok{ … }. - Een statement doet iets maar levert geen waarde:
let x = 5;, en elke expressie gevolgd door;.
Voorspel: wat gebeurt er als je breedte * hoogte; schrijft, met puntkomma?
error[E0308]: mismatched types
--> src/main.rs:1:44
|
1 | fn oppervlakte(breedte: f64, hoogte: f64) -> f64 {
| ----------- ^^^ expected `f64`, found `()`
2 | breedte * hoogte;
| - help: remove this semicolon to return this value
De puntkomma maakt van de expressie een statement; het blok levert dan () op in plaats van een f64. De compiler ziet dat en stelt voor de puntkomma weg te halen.
return bestaat wel, en gebruik je om vroeg terug te keren:
fn deel(a: i32, b: i32) -> i32 {
if b == 0 {
return 0;
}
a / b
}
fn main() {
println!("{} {}", deel(10, 3), deel(1, 0));
}
if is een expressie
fn main() {
let temp = 23;
let advies = if temp > 25 { "korte broek" } else { "trui" };
println!("{advies}");
if temp < 0 {
println!("vriest");
} else if temp < 15 {
println!("fris");
} else {
println!("lekker");
}
}
Omdat if een waarde oplevert, heeft Rust geen aparte ?:-operator nodig. Beide takken moeten dan hetzelfde type hebben. De voorwaarde moet een bool zijn: if 1 { … } compileert niet, en haakjes rond de voorwaarde zijn niet nodig.
loop, while en for
fn main() {
// for over een range
for i in 1..=5 {
print!("{i} "); // 1 2 3 4 5
}
println!();
// while
let mut n = 27;
let mut stappen = 0;
while n != 1 {
n = if n % 2 == 0 { n / 2 } else { 3 * n + 1 };
stappen += 1;
}
println!("stappen: {stappen}");
// loop met break-waarde
let mut x = 1;
let eerste_boven_1000 = loop {
x *= 3;
if x > 1000 {
break x;
}
};
println!("{eerste_boven_1000}"); // 2187
}
1..5is 1 t/m 4 (het einde telt niet mee);1..=5is 1 t/m 5.(1..=10).rev()telt terug,(0..20).step_by(5)geeft 0, 5, 10, 15.loopis een eindeloze lus; metbreak waardegeeft hij een waarde terug. Handig voor "probeer tot het lukt".forwerkt over alles wat je kunt doorlopen: ranges, arrays, en later vectors en iterators (les 17).
Een C-achtige for (i = 0; i < n; i++) bestaat niet; gebruik for i in 0..n.
Lussen met labels
Om uit een geneste lus te breken, geef je de buitenste een label:
fn main() {
'buiten: for a in 1..20 {
for b in a..20 {
if a * a + b * b == 13 * 13 {
println!("{a}² + {b}² = 13²");
break 'buiten;
}
}
}
}
FizzBuzz
fn fizzbuzz(n: u32) -> String {
match (n % 3, n % 5) {
(0, 0) => "FizzBuzz".to_string(),
(0, _) => "Fizz".to_string(),
(_, 0) => "Buzz".to_string(),
_ => n.to_string(),
}
}
fn main() {
for i in 1..=15 {
println!("{}", fizzbuzz(i));
}
}
match vergelijkt een waarde met patronen; _ betekent "maakt niet uit". In les 9 gaan we er dieper op in. String is tekst die de functie zelf maakt en teruggeeft (les 7).
Typische fouten
- Puntkomma na de returnwaarde:
expected f64, found (). - Takken van
ifmet verschillende typen:if x { 1 } else { "nee" }compileert niet. 1..10verwachten dat 10 meetelt.- Parametertype vergeten:
fn dubbel(x)is een fout.
Oefeningen
- Schrijf
fn is_schrikkeljaar(jaar: u32) -> bool(deelbaar door 4, behalve eeuwjaren, tenzij deelbaar door 400). - Wat is de waarde van
y:let y = { let x = 3; x * x + 1 };? - Druk de tafel van 7 af (1 × 7 t/m 10 × 7) met een
for-lus.
Modelantwoord
-
De hele functie is één expressie, zonderfn is_schrikkeljaar(jaar: u32) -> bool { (jaar % 4 == 0 && jaar % 100 != 0) || jaar % 400 == 0 }return. 10. Het blok is een expressie; de laatste regel zonder puntkomma is de waarde.-
fn main() { for i in 1..=10 { println!("{i:>2} × 7 = {:>2}", i * 7); } }
Lab: FizzBuzz en tafels
- Schrijf
fizzbuzz(n: u32) -> Stringen druk 1 t/m 100 af. - Schrijf
tafel(n: u32)die de tafel vannafdrukt, en roep hem aan voor 1 t/m 10. - Schrijf
is_schrikkeljaaren druk alle schrikkeljaren tussen 1890 en 2030 af. beetje check rust-les04
Samenvatting
- Functies:
fn naam(p: Type) -> Type; parametertypen zijn verplicht. - De laatste expressie zonder puntkomma is de waarde van een blok, en dus de returnwaarde.
ifenloopzijn expressies;break waardegeeft een waarde uit eenloop.for i in 0..nen0..=n; labels voor geneste lussen.
Volgende les: het belangrijkste concept van Rust, ownership.
Lab
FizzBuzz en tafels
💻 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-les4
Klaar? Controleer je werk met:
$ beetje check rust-les4