Ein Array in Rust ist eine zusammenhängende Sequenz gleichartiger Elemente mit zur Compile-Zeit bekannter Länge. Der Typ [T; N] enthält die Länge N direkt im Typ — [i32; 3] und [i32; 4] sind unterschiedliche Typen. Arrays liegen typischerweise auf dem Stack, sind günstig zu kopieren (bei Copy-Elementen) und eignen sich für alles, wo die Größe vorher feststeht: Puffer, kleine Lookup-Tables, Koordinaten-Tripel. Dieser Artikel zeigt die Syntax, die Initialisierungs-Patterns, die wichtigsten Methoden und grenzt Arrays gegen Vec und Slice ab.

Syntax und Initialisierung

Rust Array-Erstellung
fn main() {
    // Auflisten aller Elemente
    let primzahlen: [u32; 5] = [2, 3, 5, 7, 11];

    // Wiederholungs-Initialisierung: 1024 Nullen
    let puffer: [u8; 1024] = [0; 1024];

    // Type-Inference
    let zahlen = [1, 2, 3];                // [i32; 3]
    let nullen = [0_u64; 256];             // [u64; 256]

    // 2D-Array
    let matrix: [[f64; 3]; 3] = [
        [1.0, 0.0, 0.0],
        [0.0, 1.0, 0.0],
        [0.0, 0.0, 1.0],
    ];

    println!("{:?}", primzahlen);
}

Die [wert; anzahl]-Form ist besonders nützlich für initialisierte Puffer — [0; 1024] erzeugt ein 1024-Byte-Array, gefüllt mit Nullen. Voraussetzung: der wert ist Copy (für Wiederholung ohne Move).

Eigenschaften

  • Compile-Zeit-Länge. [T; N] ist ein eigener Typ pro N. Funktionen, die nur [i32; 5] akzeptieren, lehnen [i32; 4] ab. Workaround: Slice-Parameter &[i32] (siehe Slice-Artikel).
  • Stack-Allokation (bei lokalen Variablen). Schnell anzulegen, schnell freizugeben — kein Heap-Overhead.
  • Homogen. Alle Elemente vom gleichen Typ T.
  • Indexierung über usize. arr[i] mit i: usize — Out-of-Bounds-Zugriff führt zu Panic.
  • Copy, wenn T: Copy. Ein [i32; 4] lässt sich kopieren, ein [String; 4] nicht (nur Clone).

Zugriff

Rust Index-Zugriff
let arr = [10, 20, 30, 40, 50];

println!("{}", arr[0]);            // 10 — Panic-fähig
println!("{}", arr.len());         // 5
println!("{}", arr[arr.len()-1]);  // 50

// arr[10] — Panic mit "index out of bounds"

// Sicherer Zugriff
match arr.get(10) {
    Some(v) => println!("{v}"),
    None => println!("kein Element"),
}

arr[i] panickt bei Out-of-Bounds. arr.get(i) gibt Option<&T> zurück — sicher.

Wann was?

  • arr[i] für Code-Hot-Paths, wo du sicher weißt, dass der Index gültig ist. Bounds-Check macht der Compiler trotzdem — kein Unsafe nötig.
  • arr.get(i) für User-Input oder unsichere Quellen, wo Out-of-Bounds möglich ist.

Iteration

Seit Rust 1.53 (Edition 2021) implementieren Arrays direkt IntoIterator — die häufigste Stolperfalle früherer Versionen ist damit weg:

Rust Iteration
let arr = [10, 20, 30];

// Iteration über Referenzen
for &x in arr.iter() {
    println!("{x}");
}

// Direkter for-Loop (seit Edition 2021)
for x in arr {            // verbraucht das Array (move)
    println!("{x}");
}

// Index und Wert
for (i, &x) in arr.iter().enumerate() {
    println!("{i}: {x}");
}

In früheren Editionen war for x in [1, 2, 3] ein Iterator über Referenzen (&i32), nicht über Werte. Seit 2021 läuft es by-value — wer beim Wechsel altes Verhalten will, schreibt for x in [1, 2, 3].iter().

Slicing — Array als Slice

Jedes Array kann implizit als Slice referenziert werden:

Rust Array als Slice
fn summe(slice: &[i32]) -> i32 {
    slice.iter().sum()
}

fn main() {
    let arr = [1, 2, 3, 4, 5];
    let teil = &arr[1..4];          // &[i32] mit 3 Elementen

    println!("{}", summe(&arr));     // 15
    println!("{}", summe(teil));     // 9
}

&arr ist ein &[i32; 5] (Referenz aufs Array), aber durch Deref-Coercion wird daraus automatisch ein &[i32] (Slice). Funktionen, die &[T] nehmen, akzeptieren also Arrays beliebiger Länge.

&arr[1..4] extrahiert einen Teil-Slice — das ist ein eigener &[i32], nicht ein Array.

Methoden auf Arrays

Arrays haben durch ihre Slice-Coercion Zugriff auf alle Slice-Methodeniter, sort, len, contains, binary_search, chunks, windows, etc. Plus eigene Array-Methoden:

Rust Array-spezifisch
let arr = [1, 2, 3, 4, 5];

let kopiert = arr.map(|x| x * 2);       // [i32; 5] = [2, 4, 6, 8, 10]
let mut sortiert = arr;
sortiert.sort();                          // []-Methode

let summe: i32 = arr.iter().sum();
let max = arr.iter().max().unwrap();
let enthaelt_3 = arr.contains(&3);

// Zip auf gleicher Länge
let a = [1, 2, 3];
let b = ['a', 'b', 'c'];
let zipped: [(i32, char); 3] = std::array::from_fn(|i| (a[i], b[i]));

map auf Array (Stand: stable seit 1.55) ist eine eigene Methode, die ein Array gleicher Länge mit transformierten Werten zurückgibt — kein Iterator-Collect nötig.

Speicher

Ein [T; N] belegt im Speicher exakt N * size_of::<T>() Bytes plus eventuelles Padding pro Element (selten relevant).

Rust Größen
use std::mem::size_of;

println!("{}", size_of::<[u8; 1024]>());       // 1024
println!("{}", size_of::<[i32; 100]>());       // 400
println!("{}", size_of::<[(i32, i64); 5]>());  // 80 (mit Padding)

Das ist der Hauptunterschied zu Vec<T>: Arrays haben keinen Overhead (kein Heap-Pointer, kein Längenfeld). Sie sind N * sizeof(T) und nicht mehr.

Array vs. Vec vs. Slice

Drei verwandte Strukturen für Sequenzen — wann welches?

[T; N]Vec<T>&[T] / &mut [T]
LängeCompile-ZeitRuntime, wachsen möglichRuntime
SpeicherStackHeapReferenz auf Stack/Heap
Eigentumbesitzt Wertebesitzt Werteborrowt
len()konstantvariabelabhängig vom borrowt Wert
Wann nutzenfeste Größe, klein, hotwachsende Listenals Funktions-Parameter

Faustregel:

  • Arrays für feste, kleine Sequenzen (Buffer mit fester Größe, kleine Lookup-Tables, Koordinaten-Tripel).
  • Vec<T> für dynamische Listen (User-Input, Streams, Sammlungen mit unbekannter Größe).
  • Slices &[T] als Funktions-Parameter — funktioniert mit Arrays, Vecs und Sub-Slices.

Const Generics — Funktionen über alle Array-Längen

Wenn du eine Funktion schreiben willst, die mit Arrays jeder Länge funktioniert, brauchst du const generics (stable seit Rust 1.51):

Rust Const generic
fn summe<const N: usize>(arr: [i32; N]) -> i32 {
    let mut total = 0;
    for x in arr {
        total += x;
    }
    total
}

fn main() {
    println!("{}", summe([1, 2, 3]));           // 6
    println!("{}", summe([1, 2, 3, 4, 5]));     // 15
}

Const generics sind ein vollwertiges Feature mit eigenem Artikel im Generics-Kapitel. Für den Anfang reicht zu wissen: sie existieren, sie machen Funktionen über alle Array-Längen möglich.

Für Code, der einfach „eine Sequenz" verarbeitet, ist aber Slice meist die bessere Wahl:

Rust Slice-Variante (oft besser)
fn summe(slice: &[i32]) -> i32 {
    slice.iter().sum()
}

Slices funktionieren mit Arrays, Vecs und allem dazwischen — ohne Generics-Komplexität.

Praxis: Wo Arrays mit fester Länge perfekt passen

Kryptografische Hashes — [u8; 32]

SHA-256 liefert exakt 32 Bytes. Die Länge ist Teil der API-Garantie — das gehört in den Typ.

Rust SHA-256-Wrapper
#[derive(Clone, Copy)]
struct Sha256([u8; 32]);

impl Sha256 {
    fn als_hex(&self) -> String {
        self.0.iter()
            .map(|b| format!("{b:02x}"))
            .collect()
    }

    fn matches(&self, anderer: &Sha256) -> bool {
        self.0 == anderer.0
    }
}

[u8; 32] ist hier deutlich besser als Vec<u8>: keine Heap-Allokation, im Speicher genau 32 Bytes, und der Typ erzwingt die Länge — wer einen 31-Byte-Slice übergibt, kompiliert nicht.

IP-Adressen — [u8; 4] / [u8; 16]

IPv4 hat genau 4 Oktette, IPv6 genau 16. Die Stdlib nutzt intern Arrays:

Rust IPv4
struct Ipv4([u8; 4]);

impl Ipv4 {
    fn new(a: u8, b: u8, c: u8, d: u8) -> Self {
        Ipv4([a, b, c, d])
    }

    fn ist_privat(&self) -> bool {
        match self.0 {
            [10, ..]                  => true,         // 10.0.0.0/8
            [172, b, ..] if (16..=31).contains(&b) => true,  // 172.16.0.0/12
            [192, 168, ..]            => true,         // 192.168.0.0/16
            _ => false,
        }
    }
}

fn main() {
    let lokal = Ipv4::new(192, 168, 1, 1);
    assert!(lokal.ist_privat());
}

Slice-Pattern ([10, ..], [192, 168, ..]) sind die idiomatische Form für IP-Range-Tests in Rust. Klare Lesbarkeit, kein Bit-Geschiebe nötig.

Lookup-Tabellen

Statische Konstanten-Tabellen — z. B. Monatsnamen oder Tage pro Monat:

Rust Konstante Lookup-Table
const MONATSNAMEN: [&str; 12] = [
    "Januar", "Februar", "März", "April", "Mai", "Juni",
    "Juli", "August", "September", "Oktober", "November", "Dezember",
];

const TAGE_PRO_MONAT: [u8; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

fn beschreibe(monat: usize, tag: u8) -> String {
    assert!(monat >= 1 && monat <= 12);
    let max = TAGE_PRO_MONAT[monat - 1];
    assert!(tag >= 1 && tag <= max);
    format!("{}. {}", tag, MONATSNAMEN[monat - 1])
}

Const-Arrays leben im Read-only-Segment des Binaries — null Laufzeitkosten, garantiert immutable.

Fixed-Size Ring-Buffer

In Embedded oder Audio-Verarbeitung: kreisförmig laufender Puffer mit fester Kapazität.

Rust Ring-Buffer
struct RingBuffer<const N: usize> {
    data: [i16; N],
    position: usize,
}

impl<const N: usize> RingBuffer<N> {
    fn new() -> Self {
        RingBuffer { data: [0; N], position: 0 }
    }

    fn push(&mut self, sample: i16) {
        self.data[self.position] = sample;
        self.position = (self.position + 1) % N;
    }

    fn als_slice(&self) -> &[i16] {
        &self.data
    }
}

fn main() {
    let mut buffer: RingBuffer<256> = RingBuffer::new();
    buffer.push(100);
}

Der const generic <const N: usize> macht die Buffer-Größe zur Compile-Zeit fest — keine Heap-Allokation, keine Length-Checks zur Laufzeit, exakt die Bytes, die wir wollen.

Häufige Stolperfallen

[String; N] ist nicht Copy.

Der Bequemlichkeit halber denken viele, Arrays „sind immer kopierbar". In Wahrheit sind sie nur Copy, wenn das Element-Typ es auch ist. [i32; 4] ja, [String; 4] nein. Bei letzterem führt jede Übergabe by-value zu einem Move.

Out-of-Bounds panickt — keine Compile-Zeit-Prüfung bei dynamischem Index.

Auch wenn die Länge zur Compile-Zeit bekannt ist, prüft rustc Index-Zugriffe nur, wenn der Index ein literal ist. arr[10] mit arr: [i32; 5] ist ein Compile-Fehler. arr[i] mit i: usize läuft durch — Bounds-Check passiert zur Laufzeit, panickt bei Verstoß.

[T; N]-Initialisierung mit non-Copy ist tückisch.

[String::new(); 5] funktioniert NICHT — die Wiederholungs-Form [wert; N] braucht T: Copy. Lösung: std::array::from_fn(|_| String::new()) — Closure, die für jeden Index einen neuen Wert produziert. Verfügbar seit Rust 1.63.

arr.iter() ist NICHT arr.into_iter().

iter() gibt einen Iterator über Referenzen (&T), into_iter() einen Iterator über Werte (T, moves). Für [i32; N] ist der Unterschied klein (i32 ist Copy), für [String; N] ist er groß — into_iter() verbraucht das Array.

Sehr große Arrays auf dem Stack können den Stack überlaufen.

Default-Stack auf Linux ist 8 MB. Ein [u8; 10_000_000] als lokale Variable löst Stack Overflow aus. Lösung: Box::new([0u8; 10_000_000]) — die Allokation passiert auf dem Heap. Vorsicht: der Compiler kann das Array zwischenzeitlich auf den Stack legen, bevor er es in die Box verschiebt; bei Riesen-Arrays besser vec![0u8; 10_000_000] und mit try_into() zu Box<[u8; N]> konvertieren.

[T; 0] ist gültig und nützlich.

Ein Array mit Länge 0 belegt 0 Bytes und ist ein legitimer Typ. Manchmal in generischen Kontexten relevant. Iterieren liefert nichts. Index-Zugriff jeder Art panickt.

let arr: [i32; 3] = vec.try_into().unwrap(); für Vec→Array-Konvertierung.

Wenn du ein Vec<T> in ein [T; N] umwandeln willst, gibt es seit Rust 1.48 die TryFrom<Vec<T>>-Implementierung für Arrays. Die Längen müssen aber matchen — sonst gibt's einen Err. Beispiel: let arr: [u8; 4] = bytes_vec.try_into().expect("Vec hatte falsche Länge").

2D-Arrays sind [[T; M]; N], nicht [T; M][N].

Rust-Syntax: ein Array von M-Element-Arrays. Die Dimensionen werden von außen nach innen gelesen. [[f64; 3]; 5] ist ein Array mit 5 Zeilen à 3 Spalten. Initialisiert mit let m = [[0.0; 3]; 5]; — das ist [wert; outer] mit wert = [0.0; 3].

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Primitive Datentypen

Zur Übersicht