Einleitung: Warum ein Monorepository in Rust?
Wenn Sie anfangen, Rust zu lernen, begegnen Sie als Erstes einem einzelnen Projekt, das mit cargo new erstellt wurde. Aber was tun, wenn Ihr Projekt wächst: Es entstehen gemeinsame Bibliotheken, mehrere ausführbare Dateien (z. B. Server und Client) oder Sie möchten wiederkehrenden Code in ein separates Paket auslagern?
Hier kommt das Monorepository (Monorepo) ins Spiel – ein Ansatz, bei dem der Code mehrerer miteinander verbundener Projekte (Bibliotheken und ausführbare Dateien) in einem einzigen Repository gespeichert wird. Rust bietet dafür eine integrierte Lösung – Cargo Workspaces (Arbeitsbereiche).
In diesem Artikel werden wir Schritt für Schritt durchgehen, wie man ein Monorepository in Rust von Grund auf organisiert: von der Ordnerstruktur bis hin zu praktischen Beispielen. Sie erfahren, wie Sie Code richtig in Module aufteilen, lokale Abhängigkeiten einbinden und den Build-Prozess verwalten.
1. Was ist ein Cargo Workspace und wie erstellt man ihn?
Ein Arbeitsbereich (Workspace) ist eine Sammlung von Crates, die dieselbe Cargo.lock-Datei und ein gemeinsames target-Verzeichnis verwenden. Dies vermeidet Versionskonflikte bei Abhängigkeiten und beschleunigt die Kompilierung.
Erstellen der Monorepository-Struktur
Erstellen wir ein einfaches Monorepository mit zwei Crates: einer Bibliothek utils und einer ausführbaren Datei app.
mkdir my_monorepocd my_monorepocargo new utils --libcargo new appJetzt erstellen wir die Stammdatei Cargo.toml, die den Workspace deklariert:
[workspace]members = [ "utils", "app",]Wichtig: Stellen Sie sicher, dass in den Dateien utils/Cargo.toml und app/Cargo.toml kein Abschnitt [package] mit dem Feld workspace vorhanden ist – dieser wird nicht benötigt. Cargo erkennt automatisch, dass sie Teil des Workspace sind.
Projektstruktur
Nach der Erstellung erhalten Sie diese Struktur:
my_monorepo/├── Cargo.toml # Stamm-Workspace├── utils/│ ├── Cargo.toml│ └── src/│ └── lib.rs└── app/ ├── Cargo.toml └── src/ └── main.rs2. Code-Organisation: Bibliotheken und Module
Fügen wir nun Code zur Bibliothek utils hinzu. Angenommen, wir möchten allgemeine Funktionen für die Arbeit mit Zahlen und Zeichenketten erstellen.
Bibliothekscode (utils/src/lib.rs)
pub mod math;pub mod strings;Erstellen wir die Moduldateien:
// utils/src/math.rspub fn add(a: i32, b: i32) -> i32 { a + b}
pub fn multiply(a: i32, b: i32) -> i32 { a * b}// utils/src/strings.rspub fn greet(name: &str) -> String { format!("Hallo, {}!", name)}
pub fn to_uppercase(text: &str) -> String { text.to_uppercase()}Einbinden der Bibliothek in die ausführbare Datei
In der Datei app/Cargo.toml geben wir die Abhängigkeit vom lokalen Crate an:
[package]name = "app"version = "0.1.0"edition = "2021"
[dependencies]utils = { path = "../utils" }Jetzt können wir in app/src/main.rs die Funktionen aus utils verwenden:
use utils::math;use utils::strings;
fn main() { let sum = math::add(5, 10); println!("5 + 10 = {}", sum);
let greeting = strings::greet("Welt"); println!("{}", greeting);
let upper = strings::to_uppercase("rust rocks"); println!("{}", upper);}Starten Sie das Projekt aus dem Stammverzeichnis des Monorepositorys mit dem Befehl:
cargo run -p appSie sehen die Ausgabe:
5 + 10 = 15Hallo, Welt!RUST ROCKS3. Erweiterte Organisation: mehrere ausführbare Dateien
Oft müssen in einem Monorepository mehrere