Zufallszahlen in Rust mit rand
Spiele würfeln. Quizze mischen ihre Fragen. Tests brauchen Beispieldaten. Für all das brauchst du Zufallszahlen – und Rusts Standardbibliothek hat keinen fertigen Zufallszahlengenerator. Stattdessen benutzen fast alle ein bestimmtes Crate: rand.
Diese Seite zeigt die moderne API von rand 0.10. Das ist wichtig, denn rand hat in den Versionen 0.9 und 0.10 einige Namen geändert. Wenn ein älteres Tutorial thread_rng() oder gen_range() zeigt, wurde es für eine ältere Version geschrieben. Wir schauen uns die neuen Namen an und die Fehler, die du mit den alten bekommst.
rand zu deinem Projekt hinzufügen
Abschnitt betitelt „rand zu deinem Projekt hinzufügen“Führe in deinem Projektordner aus:
cargo add randDamit landet eine Zeile wie diese in deiner Cargo.toml:
[dependencies]rand = "0.10"Wenn Crates und die Cargo.toml neu für dich sind, lies zuerst diese Seite. Die „Selbst ausprobieren“-Buttons auf dieser Seite öffnen den Rust Playground, und der hat rand schon dabei.
Schnelle Zufallswerte
Abschnitt betitelt „Schnelle Zufallswerte“Für einen einzelnen Zufallswert nimmst du die freien Funktionen rand::random_range und rand::random:
fn main() { let die: u32 = rand::random_range(1..=6); let coin: bool = rand::random(); let chance: f64 = rand::random();
println!("Würfel liegt zwischen 1 und 6: {}", (1..=6).contains(&die)); println!("chance liegt in 0.0..1.0: {}", (0.0..1.0).contains(&chance)); println!("Münze zeigt Kopf: {coin}");}Hier gibt es absichtlich keinen Ausgabeblock: Die letzte Zeile ist zufällig true oder false. Klick ein paarmal auf „Selbst ausprobieren“ und schau zu, wie sie sich ändert.
random_range(1..=6)liefert eine Zahl von 1 bis 6, einschließlich 6. Mit1..6bekämst du nur 1 bis 5. Bereiche funktionieren genau wie in einer for-Schleife.random()wählt einen Wert des Typs, den du verlangst. Bei einemboolist das ein fairer Münzwurf. Bei einemf64ist es eine Zahl von0.0bis (ausschließlich)1.0.- Die Typangabe (
: u32,: bool) sagtrand, was es erzeugen soll. Ohne sie weiß Rust unter Umständen nicht, welchen Typ du willst.
Ein Generator und der Trait RngExt
Abschnitt betitelt „Ein Generator und der Trait RngExt“Wenn du viele Zufallswerte brauchst, hol dir einmal einen Generator mit rand::rng() und ruf Methoden darauf auf. Das ist etwas schneller, und diesen Stil siehst du in den meisten Programmen:
use rand::RngExt;
fn main() { let mut rng = rand::rng();
let rolls: Vec<u32> = (0..1000).map(|_| rng.random_range(1..=6)).collect(); println!("{} Würfel geworfen", rolls.len()); println!("alle zwischen 1 und 6: {}", rolls.iter().all(|r| (1..=6).contains(r)));
let x: f64 = rng.random(); println!("x liegt in 0.0..1.0: {}", (0.0..1.0).contains(&x));}Ausgabe:
1000 Würfel geworfenalle zwischen 1 und 6: truex liegt in 0.0..1.0: trueWir geben Prüfungen aus statt der Zahlen selbst. So ist die Ausgabe jedes Mal gleich, obwohl die Werte es nicht sind.
Zwei weitere Methoden, die du oft brauchen wirst:
let lucky = rng.random_bool(0.1); // in etwa 10 % der Fälle truelet rare = rng.random_ratio(1, 100); // etwa einmal in 100 Fällen trueWozu die Zeile use rand::RngExt;? Methoden wie random_range und random_bool stammen aus einem Trait, und die Methoden eines Traits kannst du nur aufrufen, wenn der Trait im Gültigkeitsbereich (Scope) ist. In rand 0.10 gibt es zwei Traits:
Rngist der grundlegende Trait „Ich kann zufällige Bits erzeugen“. Jeder Generator implementiert ihn.RngExtbringt die praktischen Methoden mit:random,random_range,random_boolund mehr. Er funktioniert automatisch für jedenRng.
Also: Importiere RngExt, um die Methoden aufzurufen, und nimm Rng, wenn du eine Funktion schreibst, die „irgendeinen Generator“ annimmt. Beides siehst du gleich im Würfel-Beispiel.
Reproduzierbare Ergebnisse mit einem Seed
Abschnitt betitelt „Reproduzierbare Ergebnisse mit einem Seed“rand::rng() liefert bei jedem Lauf andere Zahlen. Genau das willst du in einem echten Spiel. Aber für Tests, zum Debuggen oder für das „Level des Tages“ in einem Spiel willst du jedes Mal dieselben „zufälligen“ Zahlen.
Dafür gibt es den Seed (Startwert). Ein Generator mit Seed erzeugt für denselben Seed immer dieselbe Folge:
use rand::rngs::StdRng;use rand::{RngExt, SeedableRng};
fn main() { let mut a = StdRng::seed_from_u64(2024); let mut b = StdRng::seed_from_u64(2024);
let first: Vec<u8> = (0..4).map(|_| a.random_range(0..100)).collect(); let second: Vec<u8> = (0..4).map(|_| b.random_range(0..100)).collect();
println!("{:?}", first); println!("{:?}", second); println!("gleich? {}", first == second);}Ausgabe:
[53, 82, 74, 51][53, 82, 74, 51]gleich? trueseed_from_u64 stammt aus dem Trait SeedableRng, also musst du ihn ebenfalls importieren. Ändere 2024 in eine andere Zahl, und du bekommst eine andere Folge – aber wieder bei jedem Lauf dieselbe.
Eine ehrliche Warnung: Der Algorithmus von StdRng darf sich in einer künftigen rand-Version ändern. Die Zahlen oben stammen aus rand 0.10. Wenn deine Tests für immer exakt dieselben Zahlen liefern müssen, empfiehlt das Rand-Buch einen benannten Generator wie ChaCha8Rng (siehe „Weiterlesen“ unten).
Mischen und Auswählen aus einem Slice
Abschnitt betitelt „Mischen und Auswählen aus einem Slice“Für Listen importierst du zwei weitere Traits aus rand::seq:
SliceRandombringtshufflemit, das einen Slice direkt an Ort und Stelle mischt.IndexedRandombringtchoose(ein zufälliges Element) undsample(mehrere verschiedene Elemente) mit.
use rand::rngs::StdRng;use rand::seq::{IndexedRandom, SliceRandom};use rand::SeedableRng;
fn main() { let mut rng = StdRng::seed_from_u64(7);
let mut cards = vec!["A", "K", "Q", "J", "10"]; cards.shuffle(&mut rng); println!("gemischt: {:?}", cards);
let colors = ["rot", "grün", "blau"]; println!("gewählt: {:?}", colors.choose(&mut rng));
let team: Vec<&str> = cards.sample(&mut rng, 2).copied().collect(); println!("Team: {:?}", team);
let empty: [i32; 0] = []; println!("aus leerer Liste: {:?}", empty.choose(&mut rng));}Ausgabe:
gemischt: ["K", "J", "10", "Q", "A"]gewählt: Some("rot")Team: ["K", "J"]aus leerer Liste: Nonechoose gibt eine Option zurück, denn aus einem leeren Slice gibt es nichts auszuwählen. Diese Methoden funktionieren gleichermaßen mit Arrays, Vektoren und Slices. Ersetze den rng mit Seed durch rand::rng(), und du bekommst bei jedem Lauf eine neue Mischung.
Beispiel: ein Würfelprogramm
Abschnitt betitelt „Beispiel: ein Würfelprogramm“Schreiben wir eine Funktion, die würfelt. Der Trick steckt im Parametertyp: &mut impl Rng bedeutet „irgendein Zufallsgenerator“. Im echten Spiel übergibst du rand::rng(). In einem Test übergibst du einen StdRng mit Seed und weißt genau, was herauskommt:
use rand::rngs::StdRng;use rand::{Rng, RngExt, SeedableRng};
/// `count` Würfel mit je `sides` Seiten werfen.fn roll(rng: &mut impl Rng, count: u32, sides: u32) -> Vec<u32> { (0..count).map(|_| rng.random_range(1..=sides)).collect()}
fn main() { let mut rng = StdRng::seed_from_u64(1);
for (count, sides) in [(2, 6), (1, 20), (3, 4)] { let rolls = roll(&mut rng, count, sides); let total: u32 = rolls.iter().sum(); println!("{count}W{sides}: {:?} = {total}", rolls); }}Ausgabe:
2W6: [5, 6] = 111W20: [16] = 163W4: [3, 1, 2] = 62W6 ist die Kurzschreibweise aus Brett- und Rollenspielen für „zwei sechsseitige Würfel“ (auf Englisch schreibt man 2d6). Den Generator hineinzureichen, statt rand::rng() innerhalb von roll aufzurufen, ist eine gute Angewohnheit: So bleibt die Funktion leicht testbar.
Beispiel: ein Generator für zufällige Passwörter
Abschnitt betitelt „Beispiel: ein Generator für zufällige Passwörter“Hier wählen wir 16 Zeichen aus einer erlaubten Menge. Die Menge lässt leicht verwechselbare Zeichen wie 0/O und 1/l weg, damit sich das Passwort leichter eintippen lässt:
use rand::seq::IndexedRandom;use rand::Rng;
const CHARSET: &[u8] = b"ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%&*";
fn password(rng: &mut impl Rng, length: usize) -> String { (0..length) .map(|_| *CHARSET.choose(rng).unwrap() as char) .collect()}
fn main() { let mut rng = rand::rng(); let pw = password(&mut rng, 16);
println!("Länge: {}", pw.len()); println!("nur erlaubte Zeichen: {}", pw.bytes().all(|b| CHARSET.contains(&b))); // println!("{pw}"); // Kommentarzeichen entfernen, um es zu sehen: bei jedem Lauf anders}Ausgabe:
Länge: 16nur erlaubte Zeichen: trueb"..." ist ein Byte-String: ein &[u8] aus ASCII-Zeichen. choose wählt ein Byte, und as char macht daraus ein Zeichen. Beachte, dass wir hier rand::rng() benutzt haben und niemals einen Generator mit Seed. Ein Passwort aus einem festen Seed ist jedes Mal dasselbe Passwort – und damit gar kein Passwort.
Zurück zum Ratespiel
Abschnitt betitelt „Zurück zum Ratespiel“Das Ratespiel-Projekt wählt seine Geheimzahl mit einem Trick über die Uhrzeit, damit es nur mit der Standardbibliothek auskommt. Mit rand wird die ganze Funktion secret_number zu einer einzigen Zeile:
let secret: u32 = rand::random_range(1..=100);Alles andere im Spiel bleibt gleich. Hier ist eine Version, die du im Playground ausführen kannst. Die Geheimzahl kommt aus einem Generator mit Seed, und der Computer rät, indem er immer die Mitte des verbleibenden Bereichs nimmt:
use rand::rngs::StdRng;use rand::{RngExt, SeedableRng};use std::cmp::Ordering;
fn main() { // Im echten Spiel: let secret: u32 = rand::random_range(1..=100); let mut rng = StdRng::seed_from_u64(99); let secret: u32 = rng.random_range(1..=100);
let (mut low, mut high) = (1, 100); let mut attempts = 0; loop { let guess = (low + high) / 2; attempts += 1; match guess.cmp(&secret) { Ordering::Less => { println!("{guess} ist zu klein"); low = guess + 1; } Ordering::Greater => { println!("{guess} ist zu groß"); high = guess - 1; } Ordering::Equal => { println!("{guess} ist richtig! Gefunden nach {attempts} Versuchen."); break; } } }}Ausgabe:
50 ist zu klein75 ist zu klein88 ist zu klein94 ist zu klein97 ist zu klein99 ist richtig! Gefunden nach 6 Versuchen.Übrigens: Mit dieser Strategie „Bereich halbieren“ brauchst du für 1 bis 100 nie mehr als 7 Versuche.
Ist rand sicher für Passwörter und Schlüssel?
Abschnitt betitelt „Ist rand sicher für Passwörter und Schlüssel?“Die kurze Antwort: rand::rng() ist so gebaut, dass es schwer vorherzusagen ist. Für wirklich wichtige Geheimnisse nimmst du aber Werkzeuge, die genau dafür gemacht sind.
rand::rng()benutzt einen kryptografisch sicheren Algorithmus (derzeit ChaCha12). Er bekommt seinen Seed aus der Zufallsquelle deines Betriebssystems, über das Crategetrandom, und erneuert ihn regelmäßig. Die rand-Dokumentation nennt ihn einen „schnellen, einigermaßen sicheren Generator“ und sagt, dass sie nicht versprechen kann, dass er jede Sicherheitsanforderung erfüllt.- Für Verschlüsselungsschlüssel, API-Tokens und andere wirklich kritische Geheimnisse holst du dir die Bytes direkt vom Betriebssystem mit
getrandom(oderrand::rngs::SysRng) – oder noch besser: Lass eine spezialisierte Kryptografie-Bibliothek den Schlüssel für dich erzeugen. - Benutze niemals einen Generator mit Seed (
seed_from_u64) oder den schnellenSmallRngfür etwas Geheimes. Wer den Seed kennt oder errät, kann dieselben „zufälligen“ Werte erzeugen.
Für Spiele, Simulationen, Mischen und Testdaten ist rand::rng() genau richtig.
Häufige Fehler
Abschnitt betitelt „Häufige Fehler“Rng statt RngExt importieren. In rand 0.9 lagen die Methoden auf Rng, deshalb steht in älterem Code use rand::Rng;. Mit rand 0.10 ergibt das:
use rand::Rng;
fn main() { let mut rng = rand::rng(); let n = rng.random_range(1..=6); println!("{n}");}error[E0599]: no method named `random_range` found for struct `ThreadRng` in the current scope --> src/main.rs:5:17 |5 | let n = rng.random_range(1..=6); | ^^^^^^^^^^^^ | = help: items from traits can only be used if the trait is in scopehelp: trait `RngExt` which provides `random_range` is implemented but not in scope; perhaps you want to import it |1 + use rand::RngExt; |Der Compiler sagt: Für ThreadRng gibt es im aktuellen Scope keine Methode random_range, denn Methoden aus Traits sind nur nutzbar, wenn der Trait im Scope ist. Er nennt dir auch gleich die Lösung: use rand::RngExt;.
Code für rand 0.8 abschreiben. Sehr alte Beispiele benutzen thread_rng() und gen_range():
fn main() { let n = rand::thread_rng().gen_range(1..=6); println!("{n}");}error[E0425]: cannot find function `thread_rng` in crate `rand` --> src/main.rs:2:19 |2 | let n = rand::thread_rng().gen_range(1..=6); | ^^^^^^^^^^ not found in `rand`Die Funktion thread_rng gibt es im Crate rand nicht mehr. Die neuen Namen sind rand::rng() und random_range(). Genauso wurde aus gen() jetzt random() und aus gen_bool() jetzt random_bool().
Bereiche, die um eins daneben liegen. random_range(1..6) liefert nie 6. Für einen Würfel schreibst du 1..=6.
Zusammenfassung
Abschnitt betitelt „Zusammenfassung“- Füge das Crate mit
cargo add randhinzu. Rusts Standardbibliothek hat keinen eigenen Zufallszahlengenerator. - Für schnelle Werte nimmst du
rand::random_range(1..=6)undrand::random::<T>(). - Für viele Werte nimmst du
let mut rng = rand::rng();zusammen mituse rand::RngExt;für Methoden wierandom_rangeundrandom_bool. SliceRandom::shufflemischt einen Slice;IndexedRandom::choosewählt ein Element und gibt eineOptionzurück.StdRng::seed_from_u64(n)liefert bei jedem Lauf dieselbe Folge: super für Tests und wiederholbare Spiel-Level, aber niemals für Geheimnisse.- Für Schlüssel und Tokens nimmst du die Zufallsquelle des Betriebssystems (
getrandom) oder ein spezialisiertes Kryptografie-Crate.
Kurzer Check
Abschnitt betitelt „Kurzer Check“Kurzer Check
1. Welcher Aufruf kann 6 liefern?
2. Mit rand 0.10 schlägt rng.random_range(..) mit „no method named random_range“ fehl. Was fehlt?
3. Wozu benutzt du StdRng::seed_from_u64(42)?
4. Was gibt choose zurück, wenn du es auf einem leeren Slice aufrufst?
Punkte: 0 / 4
Weiterlesen
Abschnitt betitelt „Weiterlesen“- The Rust Rand Book (auf Englisch): der offizielle Leitfaden zu
rand, vom Schnellstart bis zur Wahl des passenden Generators. - API-Dokumentation von
rand– docs.rs (auf Englisch): jede Funktion, jeder Trait und jeder Generator, mit Beispielen. - Updating to 0.10 – The Rust Rand Book (auf Englisch): was sich in 0.10 geändert hat, einschließlich der Umbenennung von
Rng/RngExt. Hilfreich, wenn du auf älteren Code triffst. - Our RNGs – The Rust Rand Book (auf Englisch): vergleicht die Generatoren, auch im Hinblick darauf, welche für Sicherheitszwecke geeignet sind.
- Reproducibility – The Rust Rand Book (auf Englisch): wie du Ergebnisse mit Seed über Versionen und Plattformen hinweg stabil hältst.