EMZETT.
Login

Kurz: fn main() { let b = Box::new(5); println!(”{} {}”, b, *b + 1); let l = Element(1, Box::new(Element(2, Box::new(Ende)))); println!(“{l:?}”); } `output 5 6 Element(1, Element(2, Ende))

Teil des Kurses Rust

Kapitel 11 von 13 im Kurs Rust (Abschnitt „Fortgeschritten“). Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

Box: Heap-Allokation

#[derive(Debug)]
enum Liste {
    Element(i32, Box<Liste>),
    Ende,
}
use Liste::{Element, Ende};
 
fn main() {
    let b = Box::new(5);
    println!("{} {}", b, *b + 1);
    let l = Element(1, Box::new(Element(2, Box::new(Ende))));
    println!("{l:?}");
}

Ausgabe:

5 6
Element(1, Element(2, Ende))

Box<T> legt einen Wert auf den Heap. Rekursive Typen brauchen es, damit ihre Größe bekannt ist.

Rc und RefCell: geteilter Besitz

Rc<T> (reference counted) erlaubt mehrere Besitzer (nur ein Thread). RefCell<T> verschiebt die Leihregeln auf die Laufzeit (innere Veränderlichkeit).

use std::cell::RefCell;
use std::rc::Rc;
 
#[derive(Debug)]
struct Konto { stand: i32 }
 
fn main() {
    let a = Rc::new(RefCell::new(Konto { stand: 100 }));
    let b = Rc::clone(&a);
    println!("Besitzer: {}", Rc::strong_count(&a));
    b.borrow_mut().stand += 50;
    a.borrow_mut().stand -= 30;
    println!("{:?}", a.borrow());
    {
        let _c = Rc::clone(&a);
        println!("Besitzer: {}", Rc::strong_count(&a));
    }
    println!("Besitzer: {}", Rc::strong_count(&a));
}

Ausgabe:

Besitzer: 2
Konto { stand: 120 }
Besitzer: 3
Besitzer: 2

Lifetimes

Eine Lifetime sagt dem Compiler, wie lange eine Referenz gültig ist. Meist wird sie automatisch erschlossen. Bei Funktionen mit mehreren Referenzen im Rückgabewert musst du sie benennen:

fn laengerer<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() >= y.len() { x } else { y }
}
 
struct Auszug<'a> {
    teil: &'a str,
}
 
impl<'a> Auszug<'a> {
    fn erster_satz(text: &'a str) -> Auszug<'a> {
        Auszug { teil: text.split('.').next().unwrap_or(text) }
    }
}
 
fn main() {
    let a = String::from("lang genug");
    let ergebnis;
    {
        let b = String::from("kurz");
        ergebnis = laengerer(&a, &b).to_string();
    }
    println!("{ergebnis}");
    let text = String::from("Erster Satz. Zweiter Satz.");
    let ausz = Auszug::erster_satz(&text);
    println!("{}", ausz.teil);
}

Ausgabe:

lang genug
Erster Satz

Der Compiler lehnt Code ab, in dem eine Referenz länger leben würde als ihr Wert (hängende Referenz):

Fehlermeldung:

fn main() {
    let r;
    {
        let x = 5;
        r = &x;
    }
    println!("{r}");
}

Threads

use std::thread;
 
fn main() {
    let mut handles = Vec::new();
    for i in 1..=3 {
        handles.push(thread::spawn(move || i * i));
    }
    let ergebnisse: Vec<i32> = handles.into_iter().map(|h| h.join().unwrap()).collect();
    println!("{ergebnisse:?}");
}

Ausgabe:

[1, 4, 9]

Mit move wandert der Besitz der Variablen in den Thread. join() wartet und liefert den Rückgabewert.

Geteilter Zustand: Arc und Mutex

use std::sync::{Arc, Mutex};
use std::thread;
 
fn main() {
    let zaehler = Arc::new(Mutex::new(0));
    let mut handles = vec![];
    for _ in 0..8 {
        let z = Arc::clone(&zaehler);
        handles.push(thread::spawn(move || {
            for _ in 0..1000 {
                *z.lock().unwrap() += 1;
            }
        }));
    }
    for h in handles { h.join().unwrap(); }
    println!("{}", *zaehler.lock().unwrap());
}

Ausgabe:

8000

Arc ist das threadsichere Rc, Mutex sorgt für exklusiven Zugriff. Der Compiler verhindert Data Races: Ohne Arc/Mutex würde der Code nicht übersetzen.

Channels

use std::sync::mpsc;
use std::thread;
 
fn main() {
    let (tx, rx) = mpsc::channel();
    for id in 0..3 {
        let tx = tx.clone();
        thread::spawn(move || {
            tx.send(id * 10).unwrap();
        });
    }
    drop(tx);
    let mut empfangen: Vec<i32> = rx.iter().collect();
    empfangen.sort();
    println!("{empfangen:?}");
}

Ausgabe:

[0, 10, 20]

Merke

  • Box<T> Heap, Rc<T> geteilter Besitz (ein Thread), Arc<T> (mehrere Threads)
  • RefCell<T> / Mutex<T> für Veränderung bei geteiltem Besitz
  • Lifetimes ('a) beschreiben, wie lange Referenzen gültig sind
  • thread::spawn mit move, join wartet; Channels (mpsc) schicken Nachrichten
  • “Furchtlose Nebenläufigkeit”: Data Races werden zur Übersetzungszeit verhindert

Übungsaufgabe

Starte vier Threads, die jeweils 1000 zu einem Arc<Mutex<i32>> addieren, und gib das Ergebnis aus.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Iteratoren und Closures im Einsatz

Weiter: Module, Cargo und Tests

Alle Kapitel: Rust im Überblick