Rust bietet zehn Integer-Familien — fünf signed und fünf unsigned, mit festgelegten Bitbreiten von 8 bis 128 Bit, plus zwei plattformabhängige Typen usize und isize. Diese Auswahl ist deutlich differenzierter als in C (int, long, long long) oder Java (int, long) und gibt dir präzise Kontrolle über Speicherverbrauch und Wertebereich. Hinzu kommt eine eigene Antwort auf die alte Frage „Was passiert bei Overflow?" — eine Antwort, die zwischen Debug- und Release-Builds unterschiedlich ausfällt und für die Rust eine ganze Familie kontrollierbarer Arithmetik-Methoden bereithält.
Die zehn Integer-Familien
| Typ | Bitbreite | Wertebereich |
|---|---|---|
i8 | 8 | -128 bis 127 |
i16 | 16 | -32 768 bis 32 767 |
i32 | 32 | ca. -2,1 Mrd. bis 2,1 Mrd. |
i64 | 64 | ca. -9,2 · 10¹⁸ bis 9,2 · 10¹⁸ |
i128 | 128 | ca. ±1,7 · 10³⁸ |
u8 | 8 | 0 bis 255 |
u16 | 16 | 0 bis 65 535 |
u32 | 32 | 0 bis ca. 4,3 Mrd. |
u64 | 64 | 0 bis ca. 1,8 · 10¹⁹ |
u128 | 128 | 0 bis ca. 3,4 · 10³⁸ |
isize | abhängig | wie i32 auf 32-bit, wie i64 auf 64-bit Plattformen |
usize | abhängig | wie u32 auf 32-bit, wie u64 auf 64-bit Plattformen |
Die Wahl:
- Default ist
i32— performant, ausreichend für die meisten Fälle. u8ist der Standard für Bytes (z. B.Vec<u8>als Buffer).u32/i32für gewöhnliche Zahlen, IDs, Counter.u64/i64für Zeitstempel, große Counter, Dateigrößen.u128/i128für Crypto-Berechnungen, große Hashes, Currency mit hoher Präzision.usizespeziell für Indexierung und Längen —Vec::len()istusize, Slice-Index[i]erwartetusize. Auf 64-bit-Systemen istusize64 bit groß.isizeseltener — typischerweise für Pointer-Differenzen oder als Pendant zu C'sssize_t.
Literal-Schreibweisen
Rust unterstützt Literale in vier Basen, optional mit Typ-Suffix und Trennzeichen:
let dezimal = 1_000_000; // i32 (Default)
let hex = 0xFF; // i32, 255
let oktal = 0o77; // i32, 63
let binaer = 0b1010_1100; // i32, 172
let byte = b'A'; // u8, 65 (Byte-Literal)
let als_u8 = 255_u8;
let als_i64 = 9_000_000_000_i64;
let als_usize = 42usize;
let f_aus_int = 1_000_f64; // f64, 1000.0Wichtig:
- Unterstriche
_dürfen zur Lesbarkeit beliebig zwischen Ziffern stehen. Sie haben keine Bedeutung. - Suffixe legen den Typ fest. Ohne Suffix folgt Inferenz oder Default.
b'A'ist ein Byte-Literal, ergibtu8 == 65. Nicht zu verwechseln mit'A'(char, vier Bytes Unicode).
Default-Typ und Inferenz
Wenn nach allen Constraints noch kein Typ feststeht, wählt der Compiler i32 für ganzzahlige Literale:
fn main() {
let x = 42; // i32 (Default)
let y = 42 + 1; // i32
let z: u8 = 42; // u8 — Annotation überstimmt Default
}Sobald irgendwo ein konkreter Typ erscheint, übernimmt die Inferenz. Im Code-Snippet ist das Statement let z: u8 = 42 ohne Reibungsverluste — 42 wird als u8 interpretiert, der Default greift nicht.
Overflow — Debug vs. Release
Eines der wichtigsten Themen bei Integer-Arithmetik in Rust:
fn main() {
let x: u8 = 250;
let y = x + 10;
println!("{y}");
}Verhalten:
cargo run(Debug): Programm panickt mitattempt to add with overflow.cargo run --release: Programm gibt4aus — der Wert wrappt (260 mod 256 = 4).
Das ist eine bewusste Entscheidung:
- Debug-Builds wollen Bugs sichtbar machen. Overflow wird zur Laufzeit erkannt und führt zum Panic.
- Release-Builds wollen Performance. Overflow-Checks sind out — das Wrappen entspricht der CPU-Default-Semantik.
Du kannst beide Defaults pro Profil umstellen:
[profile.release]
overflow-checks = true # Auch in Release prüfen
[profile.dev]
overflow-checks = false # In Debug deaktivieren (selten sinnvoll)Wer korrekt arithmetisch arbeiten will, sollte sich aber nicht auf das Default-Verhalten verlassen, sondern die expliziten Arithmetik-Methoden nutzen.
Sichere Arithmetik — vier Varianten
Jeder Integer-Typ bietet vier explizite Arithmetik-Familien an. Sie unterscheiden sich darin, wie sie auf Overflow reagieren:
let x: u8 = 250;
// 1. checked: gibt Option zurück
let a = x.checked_add(10); // None, weil overflow
let b = x.checked_add(5); // Some(255)
// 2. wrapping: explizit wrappen (modulo)
let c = x.wrapping_add(10); // 4
// 3. saturating: an den Limits anhalten
let d = x.saturating_add(10); // 255 (Maximum)
let e = (0u8).saturating_sub(5); // 0 (Minimum)
// 4. overflowing: liefert (Wert, did_overflow)
let (f, ueberlauf) = x.overflowing_add(10);
// f = 4, ueberlauf = trueWann was nutzen:
checked_*— wenn ein Overflow ein Fehler ist (z. B. bei User-Input). LiefertOption<T>, also mit?-Operator integrierbar.wrapping_*— wenn modulare Arithmetik gewollt ist (Hash-Funktionen, Crypto, Counter, die bewusst überlaufen sollen).saturating_*— wenn Werte „abgeschnitten" werden sollen (Pixel-Werte, Audio-Samples).overflowing_*— wenn du beides brauchst: Ergebnis und Information über Overflow.
Dieselben Familien gibt es für sub, mul, div, rem, pow, shl, shr, neg.
Methoden auf Integer-Typen
Eine Auswahl aus der Standard-Bibliothek — diese Liste ist nicht vollständig, aber zeigt das Spektrum.
// Min/Max
let a = 5_i32.max(10); // 10
let b = 5_i32.min(10); // 5
let c = (-3_i32).abs(); // 3
let d = 5_i32.pow(3); // 125
// Bit-Operationen
let bits = 0b1010_u8.count_ones(); // 2
let trailing = 0b1000_u8.trailing_zeros(); // 3
let leading = 0b0001_u8.leading_zeros(); // 7
// Konvertierung zu String
let s = 42_i32.to_string(); // "42"
let hex = format!("{:x}", 255); // "ff"
let bin = format!("{:08b}", 10); // "00001010"
// Parsing aus String
let n: i32 = "42".parse().unwrap();
let m: u8 = "ff".parse::<u8>().unwrap_or(0); // Fehler — "ff" ist nicht dezimal
let m = u8::from_str_radix("ff", 16).unwrap(); // 255Integer::MIN und Integer::MAX sind Konstanten für die Grenzen — sehr nützlich:
println!("{} bis {}", i32::MIN, i32::MAX);
println!("{} bis {}", u64::MIN, u64::MAX);Casts mit as
Konvertierungen zwischen Integer-Typen brauchen einen expliziten as-Cast:
let a: i32 = 1000;
let b: u8 = a as u8; // 232 — Abschneidung
let c: i64 = a as i64; // 1000 — sichere Erweiterung
let d: i32 = -1;
let e: u32 = d as u32; // 4_294_967_295 — Bit-Pattern bleibtas ist mächtig, aber stillschweigend lossy — es schneidet, wrappt oder reinterpretiert ohne Warnung. Für Konvertierungen, die fehlschlagen können, ist TryFrom/TryInto die sichere Variante:
let a: i32 = 1000;
let b: u8 = u8::try_from(a).unwrap_or(0); // 0, da 1000 nicht in u8 passt
let c: u8 = u8::try_from(255_i32).unwrap(); // 255Dem Thema widmet sich der eigene Artikel Numeric Conversions.
Praxis: Wo welcher Integer-Typ?
Ein paar realistische Anwendungs-Domänen — sie zeigen, warum es die zehn Familien überhaupt gibt.
HTTP-Statuscodes — u16
HTTP-Statuscodes laufen von 100 (Continue) bis 599 (Custom Server Errors). Sie passen also nicht in u8 (max 255), aber u16 reicht spielend.
struct HttpResponse {
status: u16,
body: Vec<u8>,
}
impl HttpResponse {
fn ist_erfolg(&self) -> bool {
(200..300).contains(&self.status)
}
fn ist_redirect(&self) -> bool {
(300..400).contains(&self.status)
}
}Port-Nummern — u16
TCP- und UDP-Ports gehen von 0 bis 65535 — exakt der Wertebereich von u16. Genau dafür gemacht.
struct Endpoint {
host: String,
port: u16,
}
fn ist_privileged(port: u16) -> bool {
port < 1024 // Ports < 1024 brauchen Root-Rechte
}Farben — u8-Tripel
RGB-Farbkanäle haben 256 Stufen (0-255) — ein Byte pro Kanal.
#[derive(Clone, Copy)]
struct Rgb {
r: u8,
g: u8,
b: u8,
}
impl Rgb {
fn aufhellen(self, prozent: u8) -> Rgb {
let mix = |k: u8| -> u8 {
k.saturating_add(((255 - k) as u16 * prozent as u16 / 100) as u8)
};
Rgb { r: mix(self.r), g: mix(self.g), b: mix(self.b) }
}
fn als_hex(self) -> String {
format!("#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
}
}Die saturating_add-Methode garantiert: bei aufhellen(50) für einen Kanal mit Wert 230 kommt nicht ein Wrap (74), sondern 255 (Maximum) raus. Genau das, was bei Bild-Bearbeitung gewollt ist.
Datei-Offsets und -Größen — u64
Datei-Sizes über 4 GB passen nicht mehr in u32. Moderne APIs (std::fs::Metadata::len()) liefern u64.
use std::fs;
fn ist_grosse_datei(pfad: &str) -> std::io::Result<bool> {
let metadata = fs::metadata(pfad)?;
let size: u64 = metadata.len();
Ok(size > 1_000_000_000) // > 1 GB
}Slice-Index — usize
Jeder Index in eine Collection ist usize — der Compiler erzwingt es:
fn finde_naechsten_ungerade(zahlen: &[i32], start: usize) -> Option<usize> {
for i in start..zahlen.len() {
if zahlen[i] % 2 != 0 {
return Some(i);
}
}
None
}start: usize und zahlen.len(): usize — Indices und Längen sind in einer einheitlichen Welt. i32 müsste vor dem Index-Zugriff explizit gecastet werden.
Interessantes
usize heißt „mindestens groß genug für Memory-Indices“.
Auf 64-bit-Systemen ist usize 64 bit. Auf 32-bit-Embedded eingebauten Plattformen 32 bit. Wer Code für mehrere Plattformen schreibt, sollte sich darauf einstellen — eine usize-Variable hält auf 64-bit-Servern andere Maximalwerte als auf 32-bit-Targets. Für portable Datenstrukturen mit fester Größe also lieber u32 oder u64 explizit verwenden.
i128 ist real, aber langsamer.
rustc unterstützt i128/u128 auf allen Targets — auch wenn die CPU nur 64-bit-Register hat. Auf x86_64 werden 128-bit-Operationen in zwei 64-bit-Operationen aufgeteilt; auf ARM ähnlich. Wo möglich also u64 bevorzugen, außer du brauchst wirklich 128 Bit (Cryptography, UUIDs).
Suffixe sind Default-überschreibend, nicht ergänzend.
42 ist {integer} und wird zu i32 aufgelöst (Default). 42u8 ist sofort u8 — der Compiler nimmt nicht erst den Default und konvertiert dann. Bei Bit-Konstanten daher direkt suffixen: 0xFF_u8 ist klarer als 0xFF as u8.
checked_div ist auch für Division durch 0 relevant.
5_i32.checked_div(0) gibt None zurück. Normales 5 / 0 panickt. Wer ungeprüften Input verarbeitet, sollte checked_div (oder checked_rem) konsequent nutzen — das spart Panic-Behandlung mit catch_unwind.
i32::MIN.abs() ist undefined behavior — oder genauer: panic.
Der Wertebereich von i32 ist asymmetrisch: -2_147_483_648 bis 2_147_483_647. i32::MIN.abs() müsste also 2_147_483_648 ergeben — das passt nicht mehr in i32. Rust panickt im Debug-Build und wrappt im Release-Build. Lösung: i32::MIN.abs_diff(0) (gibt u32) oder die checked_abs-Methode.
Bit-Methoden helfen beim Crypto- und Embedded-Code.
count_ones, count_zeros, leading_zeros, trailing_zeros, swap_bytes, reverse_bits und rotate_left/rotate_right werden in den allermeisten Fällen direkt zu einer einzigen CPU-Instruktion kompiliert. Wer Hot-Path-Bit-Manipulation schreibt, ist mit der Stdlib bestens bedient.
Range und Integer-Iteration sind eng verzahnt.
(0..10) ist ein Range<i32>-Iterator (Default). (0u8..255) ist Range<u8>. Achte auf den Endwert: .. ist exklusiv, ..= inklusiv. (0..u32::MAX) läuft 4,3 Mrd. mal — kein Compile-Fehler, aber unter Umständen sehr lange.
Weiterführende Ressourcen
Externe Quellen
- The Rust Book – Integer Types
- Rust Reference – Numeric Types
- std::i32 – Methoden-Doku
- std::u8 – Methoden-Doku
- The Rust Book – Integer Overflow