Wie unterscheidet man Debug von Release in Rust? Wir analysieren cfg anhand realer Beispiele
\n\nHaben Sie jemals Code in Rust geschrieben und gedacht: „Im Debug-Modus wäre es toll, detailliertes Logging zu haben, und im Release alles Überflüssige zu entfernen"? Oder wollten Sie vielleicht Assertions nur während der Entwicklung aktivieren, aber im endgültigen Build deaktivieren? Wenn ja, sind Sie hier genau richtig. Heute schauen wir uns an, wie Sie mit der Magie von cfg das Verhalten von Code je nach Build-Typ steuern können. Das ist keine trockene Theorie – wir gehen den Weg von einem einfachen Flag bis hin zu einer Production-Lösung.
Das Thema ist nicht aus der Luft gegriffen: Auf Stack Overflow hat die Frage „How to check release / debug builds using cfg in Rust?" tausende Aufrufe gesammelt. Das zeigt, dass viele dies benötigen. Und wir werden es im Detail erklären.
\n\nWarum ist das wichtig? Zwei Welten – zwei Ansätze
\n\nIn Rust gibt es zwei Haupt-Build-Profile: Debug (mit dem Flag --debug oder einfach cargo build) und Release (cargo build --release). Sie unterscheiden sich nicht nur in den Optimierungen, sondern auch im Verhalten einiger integrierter Prüfungen.
Zum Beispiel:
\n- \n
- Debug: langsam, aber mit Array-Grenzprüfungen, Debug-Symbolen und Assertions. \n
- Release: schnell, aber ohne überflüssige Prüfungen – alles für die Leistung. \n
Was ist, wenn Sie möchten, dass die Debug-Version des Programms jede Aktion protokolliert, die Release-Version aber nur kritische Fehler? Oder dass ein Test-Endpunkt nur lokal verfügbar ist? Dafür gibt es cfg.
Die wichtigsten Werkzeuge: cfg!() und #[cfg()]
\n\nIn Rust gibt es zwei Möglichkeiten, den Build-Typ zu prüfen:
\n- \n
- Das Makro
cfg!()– wird während der Kompilierung ausgeführt und gibt einenboolzurück. Es wird innerhalb von Funktionen verwendet. \n - Das Attribut
#[cfg()]– schaltet einen Codeblock vollständig ein oder aus der Kompilierung aus. \n
Beide verwenden dieselben Schlüssel. Der bekannteste für unsere Aufgabe ist debug_assertions. Er ist nur im Debug-Build aktiv. Es gibt auch das Gegenteil – not(debug_assertions) für Release.
Beispiel 1: Einfachste Prüfung innerhalb einer Funktion
\n\nBeginnen wir mit dem Makro cfg!(). Angenommen, wir haben eine Funktion zur Berechnung von etwas Komplexem. Im Debug-Modus möchten wir die Eingabedaten prüfen:
fn calculate_something(x: i32) -> i32 {\n // Prüfung nur im Debug-Build\n if cfg!(debug_assertions) {\n println!("[DEBUG] calculate_something wurde mit x = {} aufgerufen", x);\n assert!(x > 0, "x muss positiv sein!");\n }\n\n // Hauptlogik\n x * 2\n}\n\nfn main() {\n let result = calculate_something(10);\n println!("Ergebnis: {}", result);\n}\n\n\nWas passiert?
\n- \n
- Bei
cargo build(Debug) – Sie sehen das Log, und wenn x ≤ 0 ist, stürzt das Programm mit einer Assertion ab. \n - Bei
cargo build --release– die Zeile mitcfg!(debug_assertions)wird zufalse, und der Compiler entfernt diesen Block als toten Code. Kein Overhead! \n
Beispiel 2: Vollständiges Ausschließen von Code mit #[cfg()]
\n\nManchmal reicht es nicht, die Ausführung zu überspringen, sondern man möchte eine Funktion oder ein Modul vollständig aus der Binary entfernen. Dafür verwenden wir das Attribut:
\n\n// Dieses Modul wird NUR im Debug-Build kompiliert\n#[cfg(debug_assertions)]\nmod debug_helpers {\n pub fn print_state(state: &str) {\n println!("[DEBUG] Aktueller Zustand: {}", state);\n }\n\n pub fn validate_data(data: &[u8]) -> bool {\n // Aufwändige Prüfung, im Release nicht nötig\n data.len() < 1000\n }\n}\n\n// Dieses Modul ist nur für Release\n#[cfg(not(debug_assertions))]\nmod release_helpers {\n pub fn print_state(_state: &str) {\n // Im Release machen wir nichts – sparen CPU\n }\n\n pub fn validate_data(_data: &[u8]) ->