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
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
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 + bohne 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.
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_HTMLDie 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:
| Konstrukt | Konvention |
|---|---|
| Funktionen, Variablen, Module | snake_case |
| Typen (Structs, Enums, Traits) | UpperCamelCase |
| Konstanten, statics | SCREAMING_SNAKE_CASE |
| Type-Parameter (Generics) | meist einzelner Großbuchstabe (T, U, E) |
| Lifetime-Parameter | kurz, kleingeschrieben ('a, 'src) |
Sichtbarkeit mit pub
Standardmäßig sind alle Items in Rust privat zum eigenen Modul. Mit pub machst du sie öffentlich:
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:
| Modifikator | Sichtbarkeit |
|---|---|
| (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 |
pub | Vollständig öffentlich |
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
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
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:
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-ParameterTmit Trait-Bounds. Bedeutet: „T mussAddundCopyimplementieren".x: T— Parameter mit dem Type-Parameter.-> T— Rückgabe auch vom Type-Parameter.
Die where-Klausel ist die Alternative bei vielen Bounds:
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:
# 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:
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:
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 mussmutsein.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
/// 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
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
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
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
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
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
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
- The Rust Book – Functions
- Rust Reference – Functions
- Rust Reference – Visibility
- Rust API Guidelines – Naming
- Rust by Example – Functions