Beetje Coderen

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.

⏱ 45 minuten leestijd + oefenen

🎯 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.
  • -> f64 is het returntype. Zonder -> geeft de functie niets terug (eigenlijk (), het "lege" type).
  • &str is 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..5 is 1 t/m 4 (het einde telt niet mee); 1..=5 is 1 t/m 5.
  • (1..=10).rev() telt terug, (0..20).step_by(5) geeft 0, 5, 10, 15.
  • loop is een eindeloze lus; met break waarde geeft hij een waarde terug. Handig voor "probeer tot het lukt".
  • for werkt 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 if met verschillende typen: if x { 1 } else { "nee" } compileert niet.
  • 1..10 verwachten dat 10 meetelt.
  • Parametertype vergeten: fn dubbel(x) is een fout.

Oefeningen

  1. Schrijf fn is_schrikkeljaar(jaar: u32) -> bool (deelbaar door 4, behalve eeuwjaren, tenzij deelbaar door 400).
  2. Wat is de waarde van y: let y = { let x = 3; x * x + 1 };?
  3. Druk de tafel van 7 af (1 × 7 t/m 10 × 7) met een for-lus.
Modelantwoord
  1. fn is_schrikkeljaar(jaar: u32) -> bool {
        (jaar % 4 == 0 && jaar % 100 != 0) || jaar % 400 == 0
    }
    
    De hele functie is één expressie, zonder return.
  2. 10. Het blok is een expressie; de laatste regel zonder puntkomma is de waarde.
  3. fn main() {
        for i in 1..=10 {
            println!("{i:>2} × 7 = {:>2}", i * 7);
        }
    }
    

Lab: FizzBuzz en tafels

  1. Schrijf fizzbuzz(n: u32) -> String en druk 1 t/m 100 af.
  2. Schrijf tafel(n: u32) die de tafel van n afdrukt, en roep hem aan voor 1 t/m 10.
  3. Schrijf is_schrikkeljaar en druk alle schrikkeljaren tussen 1890 en 2030 af.
  4. 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.
  • if en loop zijn expressies; break waarde geeft een waarde uit een loop.
  • for i in 0..n en 0..=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

Volg daarna de stappen van de opdracht hierboven. Nog geen Docker? Zie les 14 van Een beetje Linux & Docker. Voor dit lab is er geen live lab in de browser.

Klaar? Controleer je werk met:

$ beetje check rust-les4

📝 Quiz: functies en control flow

5 vragen · 70% nodig om te slagen

1. Wat is de returnwaarde van fn f() -> i32 { 5 }?
2. Wat gebeurt er als je fn f() -> i32 { 5; } schrijft?
3. Welke getallen geeft 1..5?
4. Hoe geef je een waarde terug uit een loop?
5. Wat moet gelden voor beide takken van let x = if c { a } else { b };?