fn ist eines der ersten Schlüsselwörter, das jeder Rust-Programmierer kennenlernt — und gleichzeitig eines der vielseitigsten. Eine Funktions-Deklaration besteht aus Name, Parameter-Liste (mit Pflicht-Typen), optionalem Rückgabe-Typ und einem Body-Block. Dieser Artikel zeigt die vollständige Syntax in jeder ihrer Varianten, klärt die Naming-Konvention snake_case, geht durch die Sichtbarkeits-Modifikatoren pub, pub(crate), pub(super) und stellt zum Abschluss die generische Form vor — der Sprung-Brett ins Generics-Kapitel.

Die Grundform

Rust Minimale fn
fn begruessen() {
    println!("Hallo!");
}

fn main() {
    begruessen();
}

Die einfachste Form: kein Parameter, kein Rückgabe-Typ (implizit ()). Das Schlüsselwort fn, dann der Name, runde Klammern, geschweifte Klammern um den Body.

Mit Parametern und Rückgabe

Rust Mit Parametern
fn addiere(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    let summe = addiere(3, 4);
    println!("{summe}");
}

Drei syntaktische Details:

  • a: i32 — jeder Parameter hat einen verpflichtenden Typ. Keine Type-Inference auf Funktions-Signaturen.
  • -> i32 — der Rückgabe-Typ kommt nach ->. Fehlt der Pfeil, ist die Rückgabe implizit ().
  • a + b ohne Semikolon — die letzte Expression im Block ist die tail expression und damit die Rückgabe. Mehr dazu im Expressions-vs-Statements-Artikel.

Naming-Konvention: snake_case

Rust-Funktionen verwenden snake_case — kleine Buchstaben mit Unterstrichen.

Rust Naming-Beispiele
fn lese_datei(pfad: &str) {}                    // snake_case ✓
fn ist_gueltige_email(s: &str) -> bool { todo!() }
fn zaehle_zeichen(text: &str) -> usize { text.chars().count() }

// Akronyme als ein Wort kleingeschrieben
fn parse_url(s: &str) {}                        // nicht parse_URL
fn fetch_html(url: &str) {}                     // nicht fetch_HTML

Die rust-analyzer-IDE-Integration und Clippy (clippy::non_snake_case) warnen bei Verstößen. Außer du arbeitest mit FFI gegen C-Bibliotheken — dort sind andere Konventionen üblich, und du kannst sie pro-Funktion mit #[allow(non_snake_case)] erlauben.

Was nicht snake_case ist

Andere Rust-Konventionen für Vergleich:

KonstruktKonvention
Funktionen, Variablen, Modulesnake_case
Typen (Structs, Enums, Traits)UpperCamelCase
Konstanten, staticsSCREAMING_SNAKE_CASE
Type-Parameter (Generics)meist einzelner Großbuchstabe (T, U, E)
Lifetime-Parameterkurz, kleingeschrieben ('a, 'src)

Sichtbarkeit mit pub

Standardmäßig sind alle Items in Rust privat zum eigenen Modul. Mit pub machst du sie öffentlich:

Rust Sichtbarkeit
mod auth {
    // Privat zum Modul auth
    fn hash_password(pw: &str) -> String {
        todo!()
    }

    // Public — von außen aufrufbar
    pub fn login(user: &str, pw: &str) -> bool {
        let _hash = hash_password(pw);
        todo!()
    }
}

fn main() {
    auth::login("anna", "123");           // ok
    // auth::hash_password("123");        // Fehler — privat
}

Granulare Sichtbarkeit

pub ist nicht binär — Rust kennt mehrere Stufen:

ModifikatorSichtbarkeit
(kein)Nur im definierenden Modul
pub(self)Identisch — explizit „nur hier"
pub(super)Im Eltern-Modul
pub(crate)Im gesamten Crate, aber nicht für externe Konsumenten
pub(in path)In einem konkret benannten Modul
pubVollständig öffentlich
Rust Sichtbarkeits-Varianten
mod backend {
    pub(crate) fn intern_aber_im_crate() {}         // nur innerhalb dieses Crates

    pub(super) fn nur_fuer_eltern_modul() {}        // ein Modul hoch
}

pub(crate) ist besonders nützlich beim Schreiben von Libraries — du markierst interne Helfer als „crate-intern", ohne sie nach außen zu publizieren.

Funktionen ohne Body — extern und Trait

Es gibt zwei Stellen, an denen eine Funktions-Signatur ohne Body legal ist:

extern für FFI

Rust extern-Block
extern "C" {
    fn strlen(s: *const u8) -> usize;
}

Erklärt eine Funktion, die in einer C-Bibliothek lebt. Der Body kommt vom Linker. Mehr im FFI-Kapitel (unsafe).

Trait-Methoden ohne Default

Rust Trait-Signatur
trait Lesbar {
    fn lese(&self) -> String;           // Pflicht-Implementierung
    fn vorschau(&self) -> String {       // Default-Implementierung
        self.lese().chars().take(10).collect()
    }
}

Eine trait-Definition kann Methoden ohne Body deklarieren — jeder Implementierer muss sie nachreichen. Mehr im Traits-Kapitel.

Generische Funktionen — Einstieg

Eine generische Funktion hat Type-Parameter in spitzen Klammern:

Rust Mit Type-Parameter
fn doppelt<T: std::ops::Add<Output = T> + Copy>(x: T) -> T {
    x + x
}

fn main() {
    assert_eq!(doppelt(3_i32), 6);
    assert_eq!(doppelt(2.5_f64), 5.0);
}

Drei Bestandteile:

  • <T: ...> — Type-Parameter T mit Trait-Bounds. Bedeutet: „T muss Add und Copy implementieren".
  • x: T — Parameter mit dem Type-Parameter.
  • -> T — Rückgabe auch vom Type-Parameter.

Die where-Klausel ist die Alternative bei vielen Bounds:

Rust where-Klausel
fn maximum<T>(a: T, b: T) -> T
where
    T: PartialOrd,
{
    if a > b { a } else { b }
}

Lesbarer bei mehreren Type-Parametern und vielen Bounds. Mehr im Generics-Kapitel.

Type-Inference beim Aufruf

Aufrufer müssen die Type-Parameter meistens nicht angeben — der Compiler schließt sie aus den Argumenten:

Rust Inferenz beim Aufruf
# fn doppelt<T: std::ops::Add<Output = T> + Copy>(x: T) -> T { x + x }
let a = doppelt(5);              // T = i32 (Inferenz)
let b = doppelt::<f32>(2.5);     // T = f32 (explizit, Turbofish)

Lifetimes in Signaturen

Wenn eine Funktion Referenzen aufnimmt oder zurückgibt, kommen oft Lifetimes ins Spiel:

Rust Mit Lifetime
fn laengste<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() > b.len() { a } else { b }
}

<'a> deklariert ein Lifetime-Parameter, das in der Signatur dreimal verwendet wird. Die Bedeutung: beide Eingabe-Referenzen leben mindestens so lange wie 'a, und die Rückgabe-Referenz lebt ebenfalls so lange.

In den meisten Fällen schreibst du Lifetimes nicht explizit — Rust hat Elision-Regeln, die häufige Patterns automatisch ergänzen. Details im Lifetimes-Kapitel.

Methoden — Funktionen mit Receiver

Funktionen, die zu einem Typ gehören, heißen Methoden. Sie leben in impl-Blöcken:

Rust Methoden
struct Konto {
    saldo_cent: i64,
}

impl Konto {
    // Associated Function (kein self) — Konstruktor-Pattern
    fn neu() -> Konto {
        Konto { saldo_cent: 0 }
    }

    // Methode mit &self — liest, mutiert nicht
    fn saldo_in_euro(&self) -> f64 {
        self.saldo_cent as f64 / 100.0
    }

    // Methode mit &mut self — mutiert
    fn einzahlen(&mut self, cent: i64) {
        self.saldo_cent += cent;
    }

    // Methode mit self — verbraucht das Objekt
    fn schliessen(self) -> i64 {
        self.saldo_cent
    }
}

fn main() {
    let mut k = Konto::neu();
    k.einzahlen(5000);
    println!("{}", k.saldo_in_euro());          // 50.0
    let rest = k.schliessen();
    println!("{rest} Cent ausgezahlt");
}

Drei Receiver-Varianten:

  • &self — Read-Only-Zugriff. Standard für „Getter" und reine Berechnungen.
  • &mut self — Mutierende Methode. Die Struct-Instanz muss mut sein.
  • self — Verbrauchend. Die Instanz ist nach dem Aufruf nicht mehr verwendbar.

Methoden ohne self (wie neu() oben) sind Associated Functions — sie gehören dem Typ, nicht einer Instanz. Aufruf mit :: statt .: Konto::neu().

Praxis: fn-Deklarationen im echten Code

URL-Validator als Library-Funktion

Rust Library-API
/// Prüft, ob ein String eine gültige HTTPS-URL ist.
pub fn ist_https_url(s: &str) -> bool {
    s.starts_with("https://")
        && s.len() > 8
        && !s.contains(' ')
}

pub(crate) fn normalisiere_pfad(p: &str) -> String {
    p.trim_end_matches('/').to_string()
}

pub für das öffentliche Interface der Library, pub(crate) für interne Helfer. Doc-Kommentar (///) für die Public-API.

Tax-Berechnung mit klarer Signatur

Rust Steuer-Helper
pub fn brutto_aus_netto(netto_cent: u64, prozent: u8) -> u64 {
    netto_cent + (netto_cent * prozent as u64 / 100)
}

pub fn mwst_betrag(brutto_cent: u64, prozent: u8) -> u64 {
    brutto_cent * prozent as u64 / (100 + prozent as u64)
}

Klare Parameter-Namen, Integer-Arithmetik (Cent statt Float-Euros), explizite Casts mit as.

Domänen-Methoden auf einem Order-Typ

Rust Methoden-Set
pub struct Bestellung {
    id: u64,
    artikel_cent: Vec<u64>,
    premium: bool,
}

impl Bestellung {
    pub fn neu(id: u64) -> Self {
        Bestellung { id, artikel_cent: Vec::new(), premium: false }
    }

    pub fn artikel_hinzufuegen(&mut self, preis_cent: u64) {
        self.artikel_cent.push(preis_cent);
    }

    pub fn als_premium_markieren(&mut self) {
        self.premium = true;
    }

    pub fn gesamt_cent(&self) -> u64 {
        self.artikel_cent.iter().sum()
    }

    pub fn rabatt_prozent(&self) -> u8 {
        if self.premium { 15 } else { 0 }
    }
}

Typisches Pattern: Konstruktor neu(), mutable Setter, immutable Getter. Klare Receiver-Wahl pro Methode.

Higher-Order: Funktion nimmt Funktion

Rust Funktion als Parameter
pub fn anwenden<T, U, F>(werte: Vec<T>, f: F) -> Vec<U>
where
    F: Fn(T) -> U,
{
    werte.into_iter().map(f).collect()
}

fn main() {
    let zahlen = vec![1, 2, 3, 4];
    let quadrate: Vec<i32> = anwenden(zahlen, |x| x * x);
    println!("{quadrate:?}");        // [1, 4, 9, 16]
}

Generische Funktion mit drei Type-Parametern: Eingabe-Typ T, Ausgabe-Typ U, Funktions-Typ F. Der Fn(T) -> U-Trait-Bound akzeptiert sowohl normale Funktionen als auch Closures. Mehr im Closures-Kapitel.

Logger-Funktion mit Tracing-Kontext

Rust Logger
pub fn log_request(method: &str, path: &str, dauer_ms: u64) {
    let level = if dauer_ms > 1000 { "WARN" } else { "INFO" };
    eprintln!("[{level}] {method} {path} ({dauer_ms}ms)");
}

Side-Effect-Funktion mit ()-Rückgabe. Klar, ohne Type-Komplexität.

Builder-Pattern mit verkettbaren Methoden

Rust Builder
pub struct AnfrageBuilder {
    url: String,
    methode: String,
    headers: Vec<(String, String)>,
}

impl AnfrageBuilder {
    pub fn neu(url: impl Into<String>) -> Self {
        AnfrageBuilder {
            url: url.into(),
            methode: "GET".to_string(),
            headers: Vec::new(),
        }
    }

    pub fn methode(mut self, m: &str) -> Self {
        self.methode = m.to_string();
        self
    }

    pub fn header(mut self, key: &str, wert: &str) -> Self {
        self.headers.push((key.to_string(), wert.to_string()));
        self
    }

    pub fn bauen(self) -> String {
        format!("{} {}", self.methode, self.url)
    }
}

fn main() {
    let anfrage = AnfrageBuilder::neu("https://example.com")
        .methode("POST")
        .header("Accept", "application/json")
        .bauen();
    println!("{anfrage}");
}

Jede Setter-Methode nimmt self per Wert, mutiert intern und gibt Self zurück — das erlaubt Method-Chaining. Der finale .bauen()-Aufruf verbraucht den Builder und gibt das fertige Resultat.

Pfad-Hilfsfunktionen mit Lifetime

Rust Path-Helper
pub fn dateiname<'a>(pfad: &'a str) -> &'a str {
    pfad.rsplit('/').next().unwrap_or(pfad)
}

pub fn endung<'a>(pfad: &'a str) -> Option<&'a str> {
    let name = dateiname(pfad);
    name.rfind('.').map(|i| &name[i + 1..])
}

fn main() {
    assert_eq!(dateiname("/tmp/foo/bar.txt"), "bar.txt");
    assert_eq!(endung("/tmp/foo/bar.txt"), Some("txt"));
}

Lifetimes sichern: die zurückgegebene Slice-Referenz lebt so lange wie der Input. Mit Elision-Regeln könnte man <'a> weglassen — beide Formen kompilieren identisch.

Interessantes

Parameter-Typen sind nie optional.

Anders als in Python oder TypeScript verlangt Rust auf Funktions-Signaturen immer einen expliziten Typ. Type-Inference funktioniert nur lokal pro Funktion (let-Bindungen, Closures) — Signatur-Inferenz wäre für API-Stabilität gefährlich.

Default-Parameter gibt es nicht — Builder-Pattern stattdessen.

Rust bietet keine Default-Werte für Funktions-Parameter. Wer flexible APIs braucht, nutzt Builder-Pattern, Options-Struct als Parameter oder die Default-Trait. Beispiel: fn foo(opts: Options) mit Options::default().

fn main kann Result zurückgeben.

Seit Rust 1.26 ist fn main() -> Result<(), Box<dyn Error>> legal. Damit lassen sich Fehler aus main mit ? propagieren, und ein non-zero Exit-Code wird automatisch gesetzt. Praktisch in CLIs.

pub use ist nicht das gleiche wie pub fn.

pub use re-exportiert ein Item unter einem anderen Pfad. Klassisches API-Design-Pattern: interne Modul-Struktur verstecken, eine flache Public-API exponieren. Detail im Module-Kapitel.

Funktionen können verschachtelt definiert werden.

Eine fn darf innerhalb einer anderen fn stehen — die innere ist im äußeren Scope nicht sichtbar. Praktisch für lokale Hilfsfunktionen, die nicht das Modul kontaminieren sollen. Aber: innere fns können nicht auf den Scope der äußeren zugreifen. Für Closures-artiges Capturing → Closure nehmen.

Doc-Kommentare mit /// werden in cargo doc gerendert.

///-Kommentare vor einer Funktion gehören zur API-Dokumentation. Sie unterstützen Markdown, eingebettete Doc-Tests und werden auf docs.rs automatisch generiert. Pflicht für Public-API in Library-Crates.

#[must_use] markiert Rückgaben als zwingend verwendbar.

#[must_use] auf einer Funktion warnt, wenn der Rückgabewert ignoriert wird. Sehr nützlich bei Result, Option oder Builder-Methoden — der Compiler nervt, wenn jemand einen wichtigen Wert wegwirft.

const fn erlaubt Aufruf zur Compile-Zeit.

Mit dem const fn-Modifier kann eine Funktion in const/static-Kontexten ausgewertet werden. Eingeschränkt in den erlaubten Operationen (kein Heap, keine Trait-Methoden über dyn), aber für mathematische und strukturelle Hilfen praktisch.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Funktionen

Zur Übersicht