Rust-Einführung

Rust ist eine systemnahe Programmiersprache unter der Leitung von Mozilla Research, mitSpeichersicherheit、Zero-Cost-Abstraktionenundfurchtloser Nebenläufigkeitals zentrale Designziele.

Rust entstand 2006, zunächst als persönliches Projekt des Mozilla-Mitarbeiters Graydon Hoare, und wurde später im Jahr 2009 offiziell von Mozilla gesponsert, das ein Team zu seiner Entwicklung bildete.

Rusts ursprüngliches Designziel war es, die langjährigen Schmerzpunkte von C/C++ in der Systemprogrammierung zu lösen:Speichersicherheitsprobleme(Nullzeiger, hängende Zeiger, Pufferüberläufe) unddie Komplexität der nebenläufigen Programmierung(Datenrennen, Deadlocks).

Rusts Designphilosophie ist es, "Fehler zur Kompilierzeit zu beseitigen"—durch ein leistungsfähiges Typsystem und einen Ownership-Mechanismus hilft Ihnen der Compiler, Speicherfehler und Datenrennen zu erkennen, anstatt Probleme zur Laufzeit zu entdecken.

Das Maskottchen der Sprache Rust ist eine Krabbe namens Ferris, freundlich und niedlich, die die freundliche und inklusive Kultur der Rust-Community repräsentiert.

Rusts wichtigste Designziele lassen sich wie folgt zusammenfassen:

DesignzielBeschreibung
Speichersicherheit (kein GC)Garantiert Speichersicherheit zur Kompilierzeit durch das Ownership-System, ohne einen Garbage Collector zu benötigen
Zero-Cost-AbstraktionenHigh-Level-Sprachfeatures verursachen nach der Kompilierung keinen Laufzeit-Overhead und erreichen die Performance von handgeschriebenem Low-Level-Code
Furchtlose NebenläufigkeitDas Typsystem und die Ownership-Regeln eliminieren Datenrennen zur Kompilierungszeit
PraktikabilitätErmöglicht das Schreiben sowohl von Betriebssystemkernen als auch von Webanwendungen und deckt Full-Stack-Szenarien ab
Starke TypinferenzDer Compiler leitet die meisten Typen automatisch ab und reduziert Boilerplate-Code
Umfangreiche ToolchainCargo (Paketverwaltung + Build), rustfmt (Formatierung), clippy (Linting) sofort einsatzbereit

Geschichte der Programmiersprache Rust

Rust hat fast zwei Jahrzehnte Entwicklung durchlaufen, von einem persönlichen Projekt zu einer weltweit weit verbreiteten Systemsprache

ZeitVersion/EreignisBeschreibung
2006Persönliches ProjektGraydon Hoare begann, die Sprache Rust zu entwerfen
2009Mozilla-SponsoringMozilla unterstützte offiziell die Entwicklung von Rust und bildete ein Team
2010Erste öffentliche AnkündigungDer Rust-Compiler (in OCaml geschrieben) wurde erstmals auf einem Mozilla-Gipfel öffentlich vorgestellt
2011Bootstrapping abgeschlossenDer Rust-Compiler wurde in Rust selbst neu geschrieben, wodurch die Abhängigkeit von OCaml entfiel
Mai 2015Rust 1.0Erste stabile Version veröffentlicht, was die Sprache offiziell für den Produktionseinsatz bereit machte
Dezember 2018Rust 2018 EditionErste Edition veröffentlicht, Syntax und Modulsystem optimiert (Einführung des NLL-Borrow-Checkers)
2019Async-Ökosystem nimmt Gestalt anasync/await-Syntax stabilisiert, asynchrone Runtimes wie Tokio wurden ausgereift
2021Rust Foundation gegründetAWS, Google, Huawei, Microsoft und Mozilla gründeten gemeinsam die Rust Foundation
Oktober 2021Rust 2021 EditionVerbesserungen wie Disjoint Capture und IntoIterator für Arrays eingeführt
2022Vom Linux-Kernel übernommenLinux 6.1 integrierte offiziell Rust-Unterstützung und machte Rust damit zur zweiten Sprache für die Kernel-Entwicklung
Februar 2024Rust 2024 EditionWeiter optimierte Sprachkonsistenz und Entwicklererlebnis

Die Gründung der Rust Foundation im Jahr 2021 war ein Meilenstein. Fünf Technologiegiganten – AWS, Google, Huawei, Microsoft und Mozilla – verpflichteten sich gemeinsam, die langfristige unabhängige Entwicklung von Rust zu sichern und die Bedenken der Community über "Dominanz durch ein einzelnes Unternehmen" zu zerstreuen.

Rust übernimmteinen stabilen Release alle sechs Wochenals Veröffentlichungsrhythmus, verwendet aber auch denEdition-Mechanismus (der etwa alle 3 Jahre veröffentlicht wird), um inkompatible Sprachänderungen zu behandeln. Ein einzelnes Projekt kann Crates aus verschiedenen Editions mischen, und der Compiler garantiert Abwärtskompatibilität.


Kernfunktionen der Rust-Sprache

Das Funktionsspektrum von Rust dreht sich um drei Dimensionen: "sicher, effizient, praktisch". Im Folgenden schlüsseln wir die grundlegendsten Sprachfunktionen von Rust einzeln auf.

Ownership-System

Ownership ist das einzigartigste und wichtigste Merkmal von Rust. Es verwaltet Speicher zur Kompilierzeit, ohne Garbage Collector oder manuelles malloc/free.

Ownership hat drei Hauptregeln:

RegelBeschreibungBedeutung
Jeder Wert hat genau einen EigentümerEin Wert kann jeweils nur einer Variablen gehörenBeseitigt Double-Free und Dangling Pointer
Wenn der Eigentümer den Gültigkeitsbereich verlässt, wird der Wert verworfenWenn eine Variable ihren Gültigkeitsbereich verlässt, ruft Rust automatisch drop auf, um Speicher freizugebenKeine manuelle Speicherverwaltung erforderlich
Zu jedem Zeitpunkt: entweder eine veränderliche Referenz oder mehrere unveränderliche ReferenzenSich gegenseitig ausschließendes Lesen/Schreiben, koexistierendes Lesen/LesenEliminiert Datenrennen zur Kompilierzeit

Die intuitive Erfahrung auf Codeebene: Wenn eine Variable einer anderen Variable zugewiesen wird, wird die Eigentümerschaft übertragen (move), und die ursprüngliche Variable wird ungültig.

Beispiel

fn main() {
    // s1 erhält die Eigentümerschaft über den String (Daten auf dem Heap gespeichert)
    let s1 = String::from("Hello, EXAMPLE!");

    // Die Eigentümerschaft wandert von s1 nach s2; danach ist s1 nicht mehr gültig
    let s2 = s1;

    // println!("{}", s1); // Kompilierfehler! s1 wurde verschoben
    println!("{}", s2);    // Richtig: s2 ist jetzt der Eigentümer

    // Basistypen (auf dem Stack gespeichert) implementieren das Copy-Trait, daher findet kein Move statt
    let x = 42;
    let y = x;  // x ist weiterhin gültig, weil i32 Copy implementiert
    println!("x = {}, y = {}", x, y); // Beide können verwendet werden

    // Unveränderliche Referenzen: Mehrere können gleichzeitig existieren
    let s3 = &s2; // Unveränderliche Ausleihe
    let s4 = &s2; // Eine weitere unveränderliche Ausleihe, erlaubt
    println!("s3 = {}, s4 = {}", s3, s4);

    // Veränderliche Referenz: Es kann nur eine gleichzeitig existieren
    let mut data = String::from("example");
    let r = &mut data; // Veränderliche Ausleihe
    r.push_str("!");   // Den ursprünglichen Wert über eine veränderliche Referenz ändern
    println!("Nach der Änderung: {}", r);
    // Die Gültigkeitsbereiche von s3 und s4 sind beendet, daher können veränderliche Referenzen erstellt werden
}

Das Ownership-System ist ein "steiler Anstieg" in Rusts Lernkurve. Anfänger "kämpfen" oft mit dem Compiler, aber wenn man es einmal verstanden hat, wird man feststellen, dass die vom Compiler erkannten Fehler in C/C++ fatale Laufzeitfehler sein könnten.

Musterabgleich

Rustsmatch-Ausdrücke sind extrem mächtig; der Compiler wirdalle Fälle erschöpfend prüfen, um sicherzustellen, dass kein Zweig übersehen wird.

Beispiel

fn main() {
    // Abgleich mit Zahlenmuster
    let score = 85;
    match score {
        90..=100 => println!("EXAMPLE Rating: A"), // Bereichsabgleich
        60..=89  => println!("EXAMPLE Rating: B"),
        0..=59   => println!("EXAMPLE Rating: C"),
        _ => println!("Invalid score"),                // Wildcard, entspricht allen verbleibenden Fällen
    }

    // match-Destrukturierung eines Tupels
    let pair = (3, 7);
    match pair {
        (0, y) => println!("Erster ist 0, zweiter ist {}", y),
        (x, 0) => println!("Erster ist {}, zweiter ist 0", x),
        (x, y) => println!("Zwei Werte: {} und {}", x, y),
    }
}

Enums und Option/Result

Rusts Enums können Daten enthalten, und in Kombination mit match ermöglichen sie einen sicheren und ausdrucksstarken Kontrollfluss.

In der Standardbibliothek,Option<T>undResult<T, E>sind die beiden grundlegendsten Enum-Typen, die jeweils die Szenarien „möglicherweise nicht vorhanden“ und „möglicherweise fehlerhaft“ behandeln – Rust kennt weder null noch Ausnahmen.

Beispiel

use std::fs;

fn main() {
    // Option<T>: Ein Wert kann vorhanden sein oder nicht (ersetzt null)
    let numbers = vec![10, 20, 30];
    let first = numbers.get(0);   // Gibt Option<&i32> zurück
    let missing = numbers.get(5); // Gibt Option<&i32> zurück

    match first {
        Some(&val) => println!("Erstes Element: {}", val),
        None => println!("Nicht gefunden"),
    }

    // Kürzere Variante: if let
    if let Some(&val) = missing {
        println!("Gefunden: {}", val);
    } else {
        println!("Index 5 existiert nicht");  // Dieser Zweig wird erwartet
    }

    // Result<T, E>: Eine Operation kann erfolgreich sein oder fehlschlagen (ersetzt Ausnahmen)
    let content = fs::read_to_string("config.txt");
    match content {
        Ok(text) => println!("Dateiinhalt: {}", text),
        Err(e) => println!("Lesen fehlgeschlagen: {}", e),
    }
}

Rust hat keine Nullwerte und keinen try-catch-Ausnahmemechanismus. Option und Result werden zur Kompilierzeit erzwungen – wenn du vergisst zu prüfen, lässt sich der Code nicht kompilieren. Dadurch werden Nullzeiger-Ausnahmen und nicht abgefangene Ausnahmen grundlegend eliminiert.

Zero-Cost-Abstraktionen

Rusts High-Level-Features (Iteratoren, Closures, Generics) sind nach der Kompilierung vollständig äquivalent zu handgeschriebenen Low-Level-Schleifen, ohne zusätzlichen Laufzeit-Overhead.

Das bedeutet, Sie können Code in einem funktionalen Stil schreiben und dabei eine Performance erreichen, die so hoch ist wie die von C.

Beispiel

fn main() {
    let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // Iterator-Verkettung — sieht wie eine High-Level-Abstraktion aus
    // Wird nach der Kompilierung zu Maschinencode expandiert, der einer handgeschriebenen for-Schleife entspricht
    let result: Vec<i32> = data
        .iter()            // Iterator abrufen
        .filter(|&x| x % 2 == 0) // Gerade Zahlen filtern
        .map(|&x| x * x)         // Quadrieren
        .collect();              // In einen Vec sammeln

    println!("{:?}", result); // [4, 16, 36, 64, 100]
}

Furchtlose Nebenläufigkeit

Rusts Typsystem und Ownership-Regelnzur Compile-Zeiteliminieren Datenrennen. Die Traits Send und Sync markieren automatisch, ob ein Typ sicher zwischen Threads übertragen oder geteilt werden kann.

Wenn Sie versuchen, nicht threadsichere Daten zwischen Threads zu teilen, meldet der Compiler direkt einen Fehler—statt darauf zu warten, dass seltsame Nebenläufigkeitsfehler zur Laufzeit in der Produktion auftreten.

Beispiel

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    // Arc (atomare Referenzzählung) wird verwendet, um Eigentümerschaft über Threads hinweg zu teilen
    // Mutex bietet gegenseitig ausschließenden Zugriff
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for i in 0..10 {
        // Arc klonen, damit jeder Thread eine Referenz auf dieselben Daten hält
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            // lock() erwirbt den Mutex und gibt MutexGuard zurück
            let mut num = counter.lock().unwrap();
            *num += 1; // Den inneren Wert durch Dereferenzierung ändern
            println!("Thread {} hat die Akkumulation abgeschlossen, aktueller Wert: {}", i, *num);
        });
        handles.push(handle);
    }

    // Warten, bis alle Threads fertig sind
    for handle in handles {
        handle.join().unwrap();
    }

    println!("Endgültiger Zählerstand: {}", *counter.lock().unwrap());
}

Cargo: All-in-One-Build-Tool

Cargo ist Rusts offizieller Paketmanager und Build-System, das Abhängigkeitsverwaltung, Kompilierung, Tests, Dokumentationserstellung und Veröffentlichung integriert.

Im Vergleich zur fragmentierten Erfahrung im C/C++-Ökosystem, bei der CMake/Makefile und Paketmanager manuell konfiguriert werden müssen, bietet Cargo ein "Out-of-the-Box"-All-in-One-Erlebnis.

BefehlFunktionTypisches Szenario
cargo new projectEin neues Projekt erstellenEin Projekt initialisieren, wodurch Cargo.toml und src/main.rs automatisch generiert werden
cargo buildDas Projekt kompilieren (Debug-Modus)Entwicklung und Debugging
cargo build --releaseKompiliere das Projekt (optimierter Modus)Produktions-Release, aktiviert alle Compiler-Optimierungen
cargo runKompilieren und ausführenCode schnell testen
cargo testTests ausführenUnit-Tests, Integrationstests, Dokumentationstests
cargo checkSchnell prüfen, ob der Code kompiliert (ohne eine Binärdatei zu erzeugen)Echtzeit-Syntaxprüfung in IDEs
cargo doc --openDokumentation generieren und öffnenAPI-Dokumentation von Abhängigkeitsbibliotheken durchsuchen
cargo clippyCode-Stil-Prüfung (Lint)Nicht empfohlene Praktiken und potenzielle Probleme erkennen
cargo fmtCode-FormatierungCode-Stil vereinheitlichen
cargo publishAuf crates.io veröffentlichenEine Open-Source-Bibliothek veröffentlichen

cargo check ist ein sehr nützlicher Befehl während der Entwicklung. Er prüft nur Syntax und Typen und erzeugt keine ausführbare Datei, daher ist er viel schneller als cargo build. Die meisten Rust-Plugins für IDEs führen beim Speichern von Dateien automatisch cargo check aus.


Anwendungsbereiche der Rust-Sprache

Mit seiner Speichersicherheit und hohen Performance breitet sich Rust schnell in vielen Bereichen aus.

Systemprogrammierung und Hochleistungsserver

Dies ist Rusts "Heimspiel" – geeignet für Szenarien, die extreme Leistung und hohe Speichersicherheit erfordern. Asynchrone LaufzeitTokiound Web-FrameworksActix Web、Axumglänzen in Szenarien mit hoher Nebenläufigkeit.

WebAssembly

Rust isteine der besten Sprachen für das Kompilieren zu WebAssembly. Seine Zero-Cost-Abstraktionen und die GC-freie Natur machen Wasm-Artefakte klein und leistungsstark.

Frontend-FrameworksYewundLeptosermöglichen das Schreiben von SPA-Anwendungen in Rust. Figma, Cloudflare Workers usw. setzen Rust + Wasm in der Produktion stark ein.

Eingebettete Systeme und IoT

Rustsno_stdModus ermöglicht die Ausführung auf Bare Metal ohne Betriebssystem, geeignet für ressourcenbeschränkte Geräte wie MCUs und Sensoren.

RTIC(Echtzeit-Framework für Interrupt-gesteuerte Nebenläufigkeit) undEmbassy(asynchrones Embedded-Framework) sind repräsentative Projekte von Embedded Rust.

CLI-Tools

Rust kompiliert zu eigenständigen Binärdateien mit hervorragender Performance und bringt viele herausragende CLI-Tools hervor:

ToolFunktionErsetzt
ripgrep (rg)Ultraschnelle Codesuchegrep
fdSchnelle Dateisuchefinde
batDateianzeige mit Syntaxhervorhebungcat
DeltaGit-Diff mit Syntaxhervorhebunggit diff
zoxideIntelligente Verzeichnisnavigationcd
dustSpeicherplatzanalysedu

Blockchain und Web3

Rust nimmt einen wichtigen Platz im Blockchain-Bereich ein.SolanaDer Kern der Chain ist in Rust geschrieben,Sui、Aptos、Polkadot(Substrate)und andere neue öffentliche Blockchains verwenden Rust ebenfalls als ihre primäre Entwicklungssprache.

Datenbanken und Dateninfrastruktur

Hochleistungs-Datenbanken und Datenverarbeitungssysteme sind Stärken von Rust:TiKV(verteilter KV-Speicher, TiDBs Speicherschicht),SurrealDB、Polars(DataFrame-Bibliothek),Databend(Cloud-Data-Warehouse) usw. sind alle mit Rust erstellt.

Betriebssysteme und Infrastruktur

Rust ist in den Linux-Kernel eingezogen (6.1+). Google schreibt Bluetooth- und Wi-Fi-Stacks in Android mit Rust neu, und Microsoft schreibt auch einige Komponenten im Windows-Kernel mit Rust neu.

Für Hobbyprojekte istRedox OSein Unix-ähnliches Betriebssystem, das vollständig von Grund auf in Rust geschrieben wurde.


Schnellstart

Rust bietet offiziell ein äußerst praktisches Installationswerkzeug, rustup, das Compiler, Paketmanager und Dokumentation mit einem einzigen Befehl verwaltet.

Rust installieren

# Unix/macOS
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 验证安装
$ rustc --version
rustc 1.85.0 (4d91de4e4 2025-02-17)

$ cargo --version
cargo 1.85.0

Dein erstes Rust-Programm

Ein Projekt mit Cargo zu erstellen ist der am meisten empfohlene Ansatz; es generiert automatisch eine Standard-Projektstruktur.

# 创建新项目
$ cargo new hello_example
     Created binary (application) `hello_example` package

# 项目结构
hello_example/
├── Cargo.toml     # 项目元数据和依赖配置
└── src/
    └── main.rs    # 程序入口文件

Cargo.toml ist die Konfigurationsdatei für das Projekt, ähnlich wie package.json bei Node.js:

Beispiel

[package]
name = "hello_example"           # Paketname (erforderlich, durch Unterstriche getrennt)
version = "0.1.0"               # Versionsnummer (erforderlich, folgt der semantischen Versionierung)
edition = "2024"                # Rust Edition (erforderlich, beeinflusst die Syntaxregeln der Sprache)

[dependencies]
# Projektabhängigkeiten hier deklarieren, zum Beispiel:
# serde = { version = "1.0", features = ["derive"] }

Die generierte main.rs enthält standardmäßig ein einfaches Hello-World-Programm:

Beispiel

// Dateipfad: src/main.rs
// Einstiegspunkt des Rust-Programms: Jedes ausführbare Programm muss eine main-Funktion haben

fn main() {
    // println! ist ein Makro (beachte das !-Symbol danach)
    // Makros werden zur Kompilierzeit zu komplexerem Code expandiert
    println!("Hallo, EXAMPLE!");
    println!("Willkommen in der Welt der Rust-Sprache");
}
# 编译并运行
$ cargo run
   Compiling hello_example v0.1.0
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s
     Running `target/debug/hello_example`
Hello, EXAMPLE!
欢迎来到 Rust 语言的世界

Überblick über die grundlegende Syntax

Die Syntax von Rust übernimmt die Stärken funktionaler Sprachen und Systemsprachen. Im Folgenden findest du einen kurzen Überblick über die am häufigsten verwendeten Syntaxelemente; jedes Beispiel ist ein vollständiges Programm, das direkt ausgeführt werden kann.

Variablen und Veränderbarkeit

Variablen in RustStandardmäßig unveränderlich(unveränderlich). Dies zwingt Entwickler, explizit zu kennzeichnen, welche Daten sich ändern können; das macht die Absicht des Codes klarer und erleichtert dem Compiler die Optimierung.

Beispiel

fn main() {
    // Unveränderliche Variable (Standard): kann nach der Deklaration nicht neu zugewiesen werden
    let name = "EXAMPLE";
    // name = "other"; // Kompilierungsfehler! Unveränderliche Variablen können nicht neu zugewiesen werden
    println!("Name: {}", name);

    // Veränderliche Variable: explizit mit dem Schlüsselwort mut deklariert
    let mut score = 0;
    println!("Initial score: {}", score);
    score = 100;       // Kann geändert werden
    println!("Aktualisierter Punktestand: {}", score);

    // Variablen-Shadowing: eine Variable mit demselben Namen mit let neu deklarieren
    let x = 5;
    let x = x + 1;      // Die neue Variable x überdeckt das alte x mit dem Wert 6
    let x = x * 2;      // Erneut überdeckt, mit dem Wert 12
    println!("Endgültiger Wert von x: {}", x);

    // Konstante: wird zur Kompilierzeit bestimmt, der Typ muss annotiert werden, die Namenskonvention verwendet durchgängig Großbuchstaben
    const MAX_POINTS: u32 = 100_000;
    println!("Maximale Punktzahl: {}", MAX_POINTS);
}

Variablen-Shadowing ist nicht dasselbe wie mut. Shadowing verwendet let, um eine neue Variable mit demselben Namen neu zu deklarieren, wodurch sich der Typ ändern kann; mut erlaubt lediglich, den Wert derselben Variable zu ändern, wobei der Typ unverändert bleibt.

Grundlegende Datentypen

Rust ist eine statisch typisierte Sprache; der Compiler muss den Typ jeder Variable zur Kompilierzeit kennen.

KategorieTypeBeispielBeschreibung
Vorzeichenbehaftete Ganzzahleni8, i16, i32, i64, i128, isizelet x: i32 = -42;Der Standard-Ganzzahltyp ist i32
Vorzeichenlose Ganzzahlenu8, u16, u32, u64, u128, usizelet y: u64 = 100;usize wird für Indizierung und Längen verwendet
Gleitkommazahlenf32, f64let pi: f64 = 3.14;Der Standard-Gleitkommatyp ist f64
Booleanboollet ok: bool = true;true oder false
Zeichencharlet c = 'R';4 Bytes, die einen Unicode-Skalarwert darstellen
Tuple(T1, T2, ...)let t: (i32, f64) = (10, 3.14);Feste Länge, Elemente können unterschiedliche Typen haben
Array[T; N]let a: [i32; 3] = [1, 2, 3];Feste Länge, gleicher Typ, auf dem Stack allokiert
Slice&[T]let s: &[i32] = &a[..];Eine Referenzansicht auf ein Array oder Vec
Dynamisches ArrayVec<T>let v: Vec<i32> = vec![1,2,3];Variable Länge, auf dem Heap allokiert
StringString / &strlet s = String::from("hi");String ist veränderlich, &str ist eine Referenz

Kontrollfluss

Rusts Kontrollfluss umfasstif、loop、while、forundmatch. Darunter können if und loop Werte als Ausdrücke zurückgeben.

Beispiel

fn main() {
    let score = 75;

    // if ist ein Ausdruck und kann einen Wert zurückgeben (alle Zweige müssen denselben Typ zurückgeben)
    let grade = if score >= 90 {
        "A"
    } else if score >= 60 {
        "B"
    } else {
        "C"
    };
    println!("BEISPIEL Bewertung: {}", grade);

    // loop ist eine Endlosschleife (Verlassen mit break, kann einen Wert zurückgeben)
    let mut count = 0;
    let result = loop {
        count += 1;
        if count == 5 {
            break count * 2; // break, gefolgt von einem Wert, dient als Rückgabewert der Schleife
        }
    };
    println!("Rückgabewert der Schleife: {}", result);

    // while-Bedingungsschleife
    let mut n = 3;
    while n > 0 {
        println!("Countdown: {}", n);
        n -= 1;
    }

    // for-in-Iteration (am häufigsten verwendet)
    let items = ["apple", "banana", "cherry"];
    for (i, item) in items.iter().enumerate() {
        println!("Element {}: {}", i + 1, item);
    }

    // for-in-Bereichsiteration
    for num in 1..=3 { // 1..=3 steht für 1, 2, 3 (inklusive beider Enden)
        println!("Zahl: {}", num);
    }
}

Funktionen

Rust-Funktionen verwendenfnals Schlüsselwort, um deklariert zu werden, und Parameter sowie Rückgabewerte müssen explizit mit Typen annotiert werden. Der Wert des letzten Ausdrucks ist der Rückgabewert (das return-Schlüsselwort wird nicht benötigt).

Beispiel

// Gewöhnliche Funktion: nimmt zwei i32 entgegen, gibt deren Summe zurück
fn add(a: i32, b: i32) -> i32 {
    a + b  // Der letzte Ausdruck wird automatisch als Rückgabewert verwendet (Hinweis: Am Ende steht kein Semikolon)
}

// Mehrere Rückgabewerte: Verwende ein Tupel, um mehrere Werte zurückzugeben
fn divide(a: f64, b: f64) -> (f64, String) {
    if b == 0.0 {
        return (0.0, String::from("Fehler: Divisor darf nicht null sein"));
    }
    (a / b, String::from("OK")) // Gibt ein Tupel zurück
}

// Generische Funktion: verwendet Trait-Beschränkungen, um das Verhalten von Typparametern einzuschränken
fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

fn main() {
    // Aufruf einer gewöhnlichen Funktion
    let sum = add(10, 20);
    println!("10 + 20 = {}", sum);

    // Aufruf der Funktion mit mehreren Rückgabewerten
    let (result, msg) = divide(10.0, 2.0);
    println!("10 / 2 = {} ({})", result, msg);

    // Fall der Division durch null
    let (result, msg) = divide(10.0, 0.0);
    println!("10 / 0 = {} ({})", result, msg);

    // Aufruf einer generischen Funktion
    let nums = vec![3, 7, 2, 9, 5];
    println!("BEISPIEL maximale Zahl: {}", largest(&nums));
}

Structs und impl-Blöcke

Rust verwendetstruct, um Datenstrukturen zu definieren, und verwendetimpl-Blöcke, um Methoden für Structs zu implementieren. Dies ähnelt dem Methodendefinitionsstil von Go, aber die Sprachorganisation ist kompakter.

Beispiel

// Definiert ein Struct (ähnlich wie C-Struct, aber leistungsfähiger)
struct User {
    name: String,         // String-Typ, besitzt die Daten
    email: String,
    active: bool,
    login_count: u64,     // u64 ist eine vorzeichenlose 64-Bit-Ganzzahl
}

// impl-Block implementiert Methoden für User
impl User {
    // Assoziierte Funktion (wie eine statische Methode): erstellt eine neue Instanz
    fn new(name: String, email: String) -> Benutzer {
        Benutzer {
            name,
            email,
            active: true,     // Standardwert
            login_count: 0,   // Standardwert
        }
    }

    // Methode: &self repräsentiert eine unveränderliche Referenz auf die Instanz
    fn summary(&self) -> String {
        format!("{} ({}) - Anmeldeanzahl: {}", self.name, self.email, self.login_count)
    }

    // Methode: &mut self repräsentiert eine veränderliche Referenz auf die Instanz
    fn login(&mut self) {
        self.login_count += 1;
    }
}

fn main() {
    // Erstellt eine Instanz mithilfe einer assoziierten Funktion
    let mut user = User::new(
        String::from("example"),
        String::from("[email protected]"),
    );

    println!("{}", user.summary());

    // Simuliert die Anmeldung
    user.login();
    user.login();
    println!("Logged in {} times", user.login_count);
}

Häufige Beispiele

Die folgenden zwei vollständigen Beispiele, die realen Szenarien nahekommen, demonstrieren typische Verwendungen von Rust in der tatsächlichen Entwicklung.

Aufbau eines HTTP-API-Dienstes

Verwenden Sie das Axum-Framework (basierend auf der Tokio-Async-Runtime), um eine HTTP-API mit JSON-Antworten zu erstellen.

Fügen Sie zuerst die Abhängigkeiten zu Cargo.toml hinzu:

# Cargo.toml
[dependencies]
axum = "0.8"              # Web 框架
tokio = { version = "1", features = ["full"] }  # 异步运行时
serde = { version = "1", features = ["derive"] } # 序列化
serde_json = "1"           # JSON 支持

Beispiel

// Dateipfad: src/main.rs
use axum::{
    extract::Query,
    response::Json,
    routing::get,
    Router,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

// Anfrageparameter-Struktur (Deserialisierung)
#[derive(Deserialize)]
struct HelloParams {
    name: Option<String>, // Option gibt einen optionalen Parameter an
}

// Antwortstruktur (Serialisierung)
#[derive(Serialize)]
struct ApiResponse {
    code: u16,
    message: String,
    data: Option<HashMap<String, String>>, // Optionales Datenfeld
}

// Verarbeitet GET-Anfragen an /hello
// Query(HelloParams) extrahiert name automatisch aus URL-Query-Parametern
async fn hello_handler(Query(params): Query<HelloParams>) -> Json<ApiResponse> {
    let name = params.name.unwrap_or_else(|| String::from("BEISPIEL"));

    let mut data = HashMap::new();
    data.insert("name".to_string(), name.clone());

    Json(ApiResponse {
        code: 200,
        message: format!("Hallo, {}! Willkommen beim Rust-HTTP-Dienst", name),
        data: Some(data),
    })
}

// Das #[tokio::main]-Makro wandelt async fn main in eine Tokio-Runtime um
#[tokio::main]
async fn main() {
    // Den Router aufbauen
    let app = Router::new()
        .route("/hello", get(hello_handler));

    // Bindet die Adresse und startet den Dienst
    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080")
        .await
        .unwrap();

    println!("BEISPIEL-Server gestartet, lauscht auf: http://localhost:8080");
    println!("Zugriffsbeispiel: http://localhost:8080/hello?name=Rust开发者");

    axum::serve(listener, app).await.unwrap();
}
$ cargo run
EXAMPLE 服务器启动,监听地址: http://localhost:8080
访问示例: http://localhost:8080/hello?name=Rust开发者

# 使用 curl 测试
$ curl http://localhost:8080/hello?name=Rust开发者
{"code":200,"message":"你好,Rust开发者!欢迎访问 Rust HTTP 服务","data":{"name":"Rust开发者"}}

$ curl http://localhost:8080/hello
{"code":200,"message":"你好,EXAMPLE!欢迎访问 Rust HTTP 服务","data":{"name":"EXAMPLE"}}

Axum ist eines der beliebtesten Web-Frameworks im Rust-Ökosystem und wird vom Tokio-Team gepflegt. Es nutzt das Typsystem von Rust voll aus, um Vorgänge wie das Extrahieren von Routenparametern und die gemeinsame Nutzung von Zustand zur Kompilierzeit zu prüfen, wodurch Laufzeitfehler stark reduziert werden.

Batchdatei-Verarbeitungstool

Dies ist ein CLI-Tool nahe an realen Szenarien: Es durchläuft Verzeichnisse rekursiv, zählt die Anzahl der Zeilen und Größen aller Dateien und aggregiert sie nach Dateiendung.

Füge Abhängigkeiten zu Cargo.toml hinzu:

# Cargo.toml
[dependencies]
walkdir = "2"  # 递归遍历目录

Beispiel

// Dateipfad: src/main.rs
use std::collections::HashMap;
use std::fs;
use walkdir::WalkDir; // Drittanbieter-Bibliothek zum rekursiven Durchlaufen von Verzeichnissen

fn main() {
    let path = "."; // Scan beginnt im aktuellen Verzeichnis

    // Statistik: gruppiert nach Dateiendung
    // HashMap<Dateiendung, (Anzahl Dateien, Gesamtzeilen, Gesamtbytes)>
    let mut stats: HashMap<String, (usize, usize, u64)> = HashMap::new();

    // WalkDir durchläuft Verzeichnisse rekursiv und überspringt automatisch versteckte Dateien und das .git-Verzeichnis
    for entry in WalkDir::new(path)
        .into_iter()
        .filter_map(|e| e.ok()) // Filtert nicht zugängliche Einträge heraus
        .filter(|e| e.file_type().is_file()) // Nur Dateien verarbeiten
    {
        // Dateiendung ermitteln
        let ext = entry
            .path()
            .extension()
            .and_then(|e| e.to_str())
            .unwrap_or("(keine Endung)")
            .to_lowercase();

        // Dateiinhalt lesen (nur Textdateien; Binärdateien können unlesbaren Text anzeigen, beeinflussen die Statistik jedoch nicht)
        if let Ok(content) = fs::read_to_string(entry.path()) {
            let line_count = content.lines().count();
            let size = entry.metadata().map(|m| m.len()).unwrap_or(0);

            let (count, lines, total_size) = stats.entry(ext).or_insert((0, 0, 0));
            *count += 1;
            *lines += line_count;
            *total_size += size;
        }
    }

    // Nach Dateianzahl in absteigender Reihenfolge sortieren und ausgeben
    let mut sorted: Vec<_> = stats.into_iter().collect();
    sorted.sort_by(|a, b| b.1 .0.cmp(&a.1 .0));

    println!("=== BEISPIEL Verzeichnis-Dateistatistiken ===");
    println!("{:<15} {:>8} {:>10} {:>12}", "Erweiterung", "Dateianzahl", "Zeilen gesamt", "Gesamtgröße (Bytes)");
    println!("{}", "-".repeat(50));
    for (ext, (count, lines, size)) in &sorted {
        println!("{:<15} {:>8} {:>10} {:>12}", ext, count, lines, size);
    }
}
$ cargo run --release
=== EXAMPLE 目录文件统计 ===
扩展名            文件数       总行数   总大小(bytes)
--------------------------------------------------
rs                  12        856        28432
toml                 3         45         1534
md                   2        120         3412
html                 1        510        14256
(无扩展名)            1          8          210

Gängige Werkzeuge und Befehlsreferenz

Neben den integrierten Funktionen von Cargo bietet das Rust-Ökosystem eine Reihe von Werkzeugen, um die Entwicklungseffizienz zu verbessern:

BefehlFunktionBeschreibung
rustup updateDie Rust-Toolchain aktualisierenrustc, cargo usw. auf die neueste stabile Version aktualisieren
rustup component add rustfmtCode-Formatierungswerkzeug installierenCode beim Speichern automatisch formatieren
rustup component add clippyCode-Linting-Werkzeug installierenNicht empfohlene Codemuster und potenzielle Fehler finden
rustup docLokale Dokumentation öffnenDie Standardbibliothek und The Book offline anzeigen
cargo add <crate>Eine Abhängigkeit hinzufügenCargo.toml automatisch bearbeiten und die neueste Version abrufen
cargo updateAbhängigkeitsversionen aktualisierenCargo.lock gemäß den Versionsbeschränkungen in Cargo.toml aktualisieren
cargo treeAbhängigkeitsbaum anzeigenDie vollständigen Abhängigkeitsbeziehungen des Projekts visualisieren
cargo benchBenchmarks ausführenCodeleistung messen
cargo install <tool>CLI-Tools installieren, die in Rust geschrieben sindz. B. cargo install ripgrep

Hinweise und häufige Probleme

Im Folgenden werden die Herausforderungen, mit denen Rust-Anfänger am ehesten konfrontiert werden, und Best-Practice-Empfehlungen aufgeführt.

Mit dem Compiler zu kämpfen ist normal

Die Lernkurve von Rust gilt allgemein als anfangs steil und später flacher.Ownership und Borrow-Checkingsind beim Einstieg das größte Hindernis.

Wenn der Compiler den Code, den du schreibst, wiederholt ablehnt, lass dich nicht entmutigen — das zeigt gerade, dass der Compiler dir hilft, potenzielle Laufzeitfehler zu erkennen. Mit zunehmender Erfahrung gewöhnst du dich allmählich daran, in Rust zu denken, und der Compiler wird dein zuverlässigster Partner statt eines Feindes.

Der Unterschied zwischen String und &str

Dies ist das Konzept, das Rust-Anfänger am häufigsten verwechseln.Stringist ein eigener, auf dem Heap allokierter String (veränderbar),&strist eine unveränderliche Referenz auf String-Daten (eine geliehene Ansicht), die normalerweise aus der automatischen Dereferenzierung von &String abgeleitet wird.

Beispiel

fn main() {
    // String: besitzt die Daten, veränderbar, auf dem Heap
    let mut s = String::from("Hello");

    // &str: Referenz, unveränderlich, normalerweise als Funktionsparameter verwendet
    let slice: &str = "EXAMPLE"; // String-Literale sind selbst &str

    s.push_str(", world!"); // String kann geändert werden
    println!("{}", s);
    println!("{}", slice);

    // Die Verwendung von &str (statt String) für Funktionsparameter ist flexibler
    greet("EXAMPLE");          // &str kann direkt übergeben werden
    greet(&s);                // Kann auch automatisch von &String in &str umgewandelt werden
}

fn greet(name: &str) {
    println!("Hello, {}!", name);
}

Fehlerbehandlung: panic! vs Result

Rust unterscheidet zwischennicht behebbaren Fehlern(unter Verwendung vonpanic!, das Programm wird beendet) undbehebbaren Fehlern(unter Verwendung vonResultentscheidet der Aufrufer, wie damit umzugehen ist).

In Bibliothekscode sollten Sie Result zurückgeben statt panic!, um dem Aufrufer die Wahl zu lassen. In Anwendungen können Sie je nach der konkreten Situation entscheiden.

unsafe: Mit Vorsicht zu verwenden, aber notwendig

Rust stellt dasunsafeSchlüsselwort bereit, das Low-Level-Operationen wie die Manipulation von Rohzeigern und den Aufruf externer Funktionen (FFI) ermöglicht.

unsafe bedeutet nicht „Sicherheitsprüfungen zu deaktivieren“; vielmehr macht es Entwickler für den Teil der Sicherheit verantwortlich, den der Compiler nicht automatisch garantieren kann. Der meiste Anwendungscode muss unsafe nicht verwenden — wenn Sie es häufig verwenden, kann das ein Zeichen dafür sein, dass Ihr Design angepasst werden muss.

Lange Kompilierzeiten

Rust ist für lange Kompilierzeiten bekannt, besonders bei großen Projekten. Das liegt daran, dass der Compiler viele Prüfungen (Ownership-Analyse, Lifetime-Validierung, Trait-Auflösung usw.) und Optimierungen zur Kompilierzeit durchführt.

Gegenmaßnahmen: Verwenden Siecargo checkstatt cargo build für die tägliche Entwicklung; teilen Sie große Crates in Unter-Crates auf, um die inkrementelle Kompilierung zu nutzen; verwenden Siesccachezum Zwischenspeichern von Kompilierungsergebnissen; rüsten Sie die Hardware auf (insbesondere SSD und mehr CPU-Kerne).

Vorschläge für den Lernpfad

Für Rust-Anfänger wird die folgende Lernreihenfolge empfohlen:

  • Arbeiten SieDie Programmiersprache Rust(das offizielle Buch, kostenlos) durch und konzentrieren Sie sich auf die Kapitel über Ownership
  • Lesen und schreiben Sie Code auf dem Rust Playground (play.rust-lang.org), um sofortiges Compiler-Feedback zu erhalten
  • MitRustlingskleine Übungen machen, um die Syntax zu festigen
  • LiesRust by Example, lerne durch Beispiele
  • Versuche, ein kleines Projekt, mit dem du vertraut bist, in Rust neu zu schreiben, um Rusts Design-Kompromisse in der Praxis zu verstehen

Zusammenfassung

Mit seinen drei Kernvorteilen –Speichersicherheit (ohne GC)、Zero-Cost-Abstraktionenundfurchtloser Nebenläufigkeit– nimmt Rust eine einzigartige Nische in Bereichen wie Systemprogrammierung, Hochleistungsdiensten, WebAssembly und Blockchain ein.

Obwohl die Lernkurve steil ist, führen die strengen Prüfungen des Compilers in langfristigen Projekten zu extrem hoher Codequalität und Wartbarkeit – weshalb immer mehr Unternehmen und Open-Source-Projekte Rust wählen.

Rusts Vorteile und geeignete Anwendungsfälle sind wie folgt zusammengefasst:

DimensionRusts Leistung
LernkurveSteil (das Ownership-System erfordert Verständnis und Anpassung), aber die Konzepte sind stabil; einmal gelernt, profitiert man ein Leben lang davon
KompiliergeschwindigkeitRelativ langsam (umfangreiche Prüfungen zur Kompilierzeit), aber durch cargo check und inkrementelle Kompilierung abgemildert
Laufzeit-PerformanceAuf demselben Niveau wie C/C++, weit vor Sprachen mit GC
SpeichersicherheitZur Kompilierzeit garantiert, kein GC nötig, keine Nullzeiger, hängenden Zeiger oder Pufferüberläufe
NebenläufigkeitssicherheitBeseitigt Datenrennen zur Kompilierungszeit, furchtlose Nebenläufigkeit
Reife des ÖkosystemsWächst rasant, bereits sehr ausgereift in Systemprogrammierung, WebAssembly und Blockchain
Am besten geeignet fürSystemprogrammierung, leistungsstarke Backends, WebAssembly, eingebettete Systeme, Blockchain, CLI-Tools, Datenbanken
Nicht sehr geeignet fürSchnelles Prototyping (Kompilierungszeit), GUI-Desktop-Anwendungen, Spieleentwicklung (im Vergleich zu ausgereiften Engines)
Weitere Erweiterungen