Zum Inhalt springen

Module in Rust

Bisher stand jedes Beispiel in einer einzigen Datei main.rs. Für kleine Programme ist das in Ordnung. Echte Projekte teilen ihren Code aber in sinnvolle Teile auf. Dafür hat Rust das Modulsystem – mit mod, pub und use.

Ein Modul ist ein benannter Block, der zusammengehörigen Code bündelt:

mod math {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}
fn main() {
let sum = math::add(2, 3);
println!("{sum}");
}

Ausgabe:

5
▶ Selbst ausprobieren

Zwei Dinge fallen auf: Du rufst die Funktion mit math::add(...) auf und benutzt den Modulnamen dabei als Pfad. Und die Funktion ist mit pub markiert. Ohne pub wäre sie von außerhalb des Moduls nicht erreichbar.

Das solltest du dir merken, weil es viele überrascht: Der Inhalt eines Moduls ist standardmäßig privat. Wenn du im Beispiel oben pub entfernst, lässt sich der Code nicht mehr kompilieren:

mod math {
fn add(a: i32, b: i32) -> i32 { // kein `pub`
a + b
}
}
fn main() {
let sum = math::add(2, 3); // ❌ Fehler: die Funktion `add` ist privat
println!("{sum}");
}

Alles, was Code außerhalb des Moduls benutzen soll, musst du ausdrücklich mit pub markieren. Das ist Absicht: So kann ein Modul seine internen Hilfsfunktionen verstecken und nur die Teile zeigen, die als öffentliche Schnittstelle (API) gedacht sind.

Module können andere Module enthalten. So entsteht in größeren Projekten eine Baumstruktur:

mod shapes {
pub mod circle {
pub fn area(radius: f64) -> f64 {
3.14159 * radius * radius
}
}
}
fn main() {
let area = shapes::circle::area(2.0);
println!("{area}");
}
▶ Selbst ausprobieren

Jedes Mal shapes::circle::area(...) zu schreiben, wird schnell lästig. Mit use holst du einen Pfad in den aktuellen Gültigkeitsbereich (Scope) und kannst ihn dann direkt benutzen:

mod shapes {
pub mod circle {
pub fn area(radius: f64) -> f64 {
3.14159 * radius * radius
}
}
}
use shapes::circle;
fn main() {
let area = circle::area(2.0); // kürzer als shapes::circle::area
println!("{area}");
}
▶ Selbst ausprobieren

Eigentlich hast du use schon benutzt: Es ist dasselbe Schlüsselwort wie auf der Seite über HashMap (use std::collections::HashMap;).

In den Beispielen oben steht alles in einer Datei, weil der Rust Playground das so verlangt. In einem echten Projekt auf deinem Rechner bekommt meist jedes Modul seine eigene Datei. Ein Projekt mit einem Modul math sieht typischerweise so aus:

src/
├── main.rs
└── math.rs
src/main.rs
mod math; // sagt Rust: Den Code dieses Moduls findest du in math.rs
fn main() {
let sum = math::add(2, 3);
println!("{sum}");
}
src/math.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}

mod math; (mit Semikolon, ohne Rumpf) weist Rust an, den Inhalt des Moduls aus der Datei math.rs im selben Ordner zu laden. Dieses Muster brauchst du, sobald dein Projekt über eine einzige Datei hinauswächst.

Schreib ein Modul namens convert mit zwei öffentlichen Funktionen: c_to_f (Celsius nach Fahrenheit) und f_to_c (umgekehrt). Gib aus main heraus 100°C sind 212°F und 212°F sind 100°C aus.

Tipp: Die Formeln sind c * 9.0 / 5.0 + 32.0 und (f - 32.0) * 5.0 / 9.0. Vergiss pub nicht, und ruf die Funktionen mit dem Modulnamen auf, etwa convert::c_to_f(100.0).

Lösung anzeigen
mod convert {
pub fn c_to_f(c: f64) -> f64 {
c * 9.0 / 5.0 + 32.0
}
pub fn f_to_c(f: f64) -> f64 {
(f - 32.0) * 5.0 / 9.0
}
}
fn main() {
println!("100°C sind {}°F", convert::c_to_f(100.0));
println!("212°F sind {}°C", convert::f_to_c(212.0));
}

Ausgabe:

100°C sind 212°F
212°F sind 100°C
▶ Selbst ausprobieren

Beide Funktionen sind pub. Deshalb kann main sie aufrufen, obwohl es außerhalb des Moduls liegt. Entfernst du ein pub, sagt dir der Compiler, dass diese Funktion privat ist.

Schreib ein verschachteltes Modul school::grades mit einer öffentlichen Funktion letter(score: u32) -> char. Sie gibt 'A' für 90 und mehr zurück, 'B' für 80 und mehr und sonst 'C'. Schreib in grades außerdem eine private Hilfsfunktion is_at_least(score: u32, min: u32) -> bool, die letter benutzt. Hol school::grades mit use in den Scope und gib dann die Note für 95, 85 und 70 aus.

Tipp: Sowohl das innere mod grades in school als auch fn letter brauchen pub, is_at_least aber nicht. Code im selben Modul kann private Funktionen immer aufrufen.

Lösung anzeigen
mod school {
pub mod grades {
pub fn letter(score: u32) -> char {
if is_at_least(score, 90) {
'A'
} else if is_at_least(score, 80) {
'B'
} else {
'C'
}
}
// privat: nur Code innerhalb von `grades` kann das aufrufen
fn is_at_least(score: u32, min: u32) -> bool {
score >= min
}
}
}
use school::grades;
fn main() {
for score in [95, 85, 70] {
println!("{score} -> {}", grades::letter(score));
}
}

Ausgabe:

95 -> A
85 -> B
70 -> C
▶ Selbst ausprobieren

letter kann is_at_least aufrufen, weil beide im selben Modul liegen. Aus main heraus würde grades::is_at_least(95, 90) mit „function is_at_least is private“ scheitern (die Funktion ist privat). Genau darum geht es: Das Modul zeigt nach außen eine kleine öffentliche API und behält seine Hilfsfunktionen für sich.

  • mod name { ... } bündelt zusammengehörigen Code in einem benannten Modul.
  • Alles in einem Modul ist standardmäßig privat. Mit pub machst du es außerhalb des Moduls sichtbar.
  • use holt einen Pfad in den Scope, damit du ihn nicht jedes Mal komplett ausschreiben musst.
  • In einem Projekt mit mehreren Dateien lädt mod name; (ohne Rumpf) den Code des Moduls aus der passenden Datei.
  • Code in einem Modul kann dessen private Elemente immer benutzen. Halte Hilfsfunktionen also privat und mach nur sichtbar, was Aufrufer wirklich brauchen.

Kurzer Check

1. Sind Elemente in einem mod standardmäßig öffentlich oder privat?

2. Wie sagst du Rust, dass es den Code eines Moduls aus math.rs laden soll, statt ihn direkt hinzuschreiben?

Punkte: 0 / 2