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:?}");
}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));
}Besitzer: 2
Konto { stand: 120 }
Besitzer: 3
Besitzer: 2Lifetimes
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);
}lang genug Erster Satz
Der Compiler lehnt Code ab, in dem eine Referenz länger leben würde als ihr Wert (hängende Referenz):
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:?}");
}[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());
}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:?}");
}[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::spawnmitmove,joinwartet; Channels (mpsc) schicken Nachrichten- "Furchtlose Nebenläufigkeit": Data Races werden zur Übersetzungszeit verhindert
Aufgabe
Starte vier Threads, die jeweils 1000 zu einem Arc<Mutex<i32>> addieren, und gib das Ergebnis aus.