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

TypBitbreiteWertebereich
i88-128 bis 127
i1616-32 768 bis 32 767
i3232ca. -2,1 Mrd. bis 2,1 Mrd.
i6464ca. -9,2 · 10¹⁸ bis 9,2 · 10¹⁸
i128128ca. ±1,7 · 10³⁸
u880 bis 255
u16160 bis 65 535
u32320 bis ca. 4,3 Mrd.
u64640 bis ca. 1,8 · 10¹⁹
u1281280 bis ca. 3,4 · 10³⁸
isizeabhängigwie i32 auf 32-bit, wie i64 auf 64-bit Plattformen
usizeabhängigwie u32 auf 32-bit, wie u64 auf 64-bit Plattformen

Die Wahl:

  • Default ist i32 — performant, ausreichend für die meisten Fälle.
  • u8 ist der Standard für Bytes (z. B. Vec<u8> als Buffer).
  • u32 / i32 für gewöhnliche Zahlen, IDs, Counter.
  • u64 / i64 für Zeitstempel, große Counter, Dateigrößen.
  • u128 / i128 für Crypto-Berechnungen, große Hashes, Currency mit hoher Präzision.
  • usize speziell für Indexierung und LängenVec::len() ist usize, Slice-Index [i] erwartet usize. Auf 64-bit-Systemen ist usize 64 bit groß.
  • isize seltener — typischerweise für Pointer-Differenzen oder als Pendant zu C's ssize_t.

Literal-Schreibweisen

Rust unterstützt Literale in vier Basen, optional mit Typ-Suffix und Trennzeichen:

Rust Literale
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.0

Wichtig:

  • 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, ergibt u8 == 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:

Rust i32-Default
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:

Rust Klassischer Overflow
fn main() {
    let x: u8 = 250;
    let y = x + 10;
    println!("{y}");
}

Verhalten:

  • cargo run (Debug): Programm panickt mit attempt to add with overflow.
  • cargo run --release: Programm gibt 4 aus — 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:

Rust Cargo.toml
[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:

Rust Vier Varianten
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 = true

Wann was nutzen:

  • checked_* — wenn ein Overflow ein Fehler ist (z. B. bei User-Input). Liefert Option<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.

Rust Nützliche Methoden
// 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();  // 255

Integer::MIN und Integer::MAX sind Konstanten für die Grenzen — sehr nützlich:

Rust MIN/MAX
println!("{} bis {}", i32::MIN, i32::MAX);
println!("{} bis {}", u64::MIN, u64::MAX);

Casts mit as

Konvertierungen zwischen Integer-Typen brauchen einen expliziten as-Cast:

Rust as-Casts
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 bleibt

as 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:

Rust TryFrom für sichere Casts
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();        // 255

Dem 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.

Rust HTTP-Response-Status
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.

Rust Netzwerk-Endpoint
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.

Rust RGB-Farbe
#[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.

Rust Datei-Verarbeitung
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:

Rust Index-Berechnung
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

/ Weiter

Zurück zu Primitive Datentypen

Zur Übersicht