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:
| Designziel | Beschreibung |
|---|---|
| Speichersicherheit (kein GC) | Garantiert Speichersicherheit zur Kompilierzeit durch das Ownership-System, ohne einen Garbage Collector zu benötigen |
| Zero-Cost-Abstraktionen | High-Level-Sprachfeatures verursachen nach der Kompilierung keinen Laufzeit-Overhead und erreichen die Performance von handgeschriebenem Low-Level-Code |
| Furchtlose Nebenläufigkeit | Das Typsystem und die Ownership-Regeln eliminieren Datenrennen zur Kompilierungszeit |
| Praktikabilität | Ermöglicht das Schreiben sowohl von Betriebssystemkernen als auch von Webanwendungen und deckt Full-Stack-Szenarien ab |
| Starke Typinferenz | Der Compiler leitet die meisten Typen automatisch ab und reduziert Boilerplate-Code |
| Umfangreiche Toolchain | Cargo (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
| Zeit | Version/Ereignis | Beschreibung |
|---|---|---|
| 2006 | Persönliches Projekt | Graydon Hoare begann, die Sprache Rust zu entwerfen |
| 2009 | Mozilla-Sponsoring | Mozilla unterstützte offiziell die Entwicklung von Rust und bildete ein Team |
| 2010 | Erste öffentliche Ankündigung | Der Rust-Compiler (in OCaml geschrieben) wurde erstmals auf einem Mozilla-Gipfel öffentlich vorgestellt |
| 2011 | Bootstrapping abgeschlossen | Der Rust-Compiler wurde in Rust selbst neu geschrieben, wodurch die Abhängigkeit von OCaml entfiel |
| Mai 2015 | Rust 1.0 | Erste stabile Version veröffentlicht, was die Sprache offiziell für den Produktionseinsatz bereit machte |
| Dezember 2018 | Rust 2018 Edition | Erste Edition veröffentlicht, Syntax und Modulsystem optimiert (Einführung des NLL-Borrow-Checkers) |
| 2019 | Async-Ökosystem nimmt Gestalt an | async/await-Syntax stabilisiert, asynchrone Runtimes wie Tokio wurden ausgereift |
| 2021 | Rust Foundation gegründet | AWS, Google, Huawei, Microsoft und Mozilla gründeten gemeinsam die Rust Foundation |
| Oktober 2021 | Rust 2021 Edition | Verbesserungen wie Disjoint Capture und IntoIterator für Arrays eingeführt |
| 2022 | Vom Linux-Kernel übernommen | Linux 6.1 integrierte offiziell Rust-Unterstützung und machte Rust damit zur zweiten Sprache für die Kernel-Entwicklung |
| Februar 2024 | Rust 2024 Edition | Weiter 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:
| Regel | Beschreibung | Bedeutung |
|---|---|---|
| Jeder Wert hat genau einen Eigentümer | Ein Wert kann jeweils nur einer Variablen gehören | Beseitigt Double-Free und Dangling Pointer |
| Wenn der Eigentümer den Gültigkeitsbereich verlässt, wird der Wert verworfen | Wenn eine Variable ihren Gültigkeitsbereich verlässt, ruft Rust automatisch drop auf, um Speicher freizugeben | Keine manuelle Speicherverwaltung erforderlich |
| Zu jedem Zeitpunkt: entweder eine veränderliche Referenz oder mehrere unveränderliche Referenzen | Sich gegenseitig ausschließendes Lesen/Schreiben, koexistierendes Lesen/Lesen | Eliminiert 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
// 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
// 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
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
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::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.
| Befehl | Funktion | Typisches Szenario |
|---|---|---|
| cargo new project | Ein neues Projekt erstellen | Ein Projekt initialisieren, wodurch Cargo.toml und src/main.rs automatisch generiert werden |
| cargo build | Das Projekt kompilieren (Debug-Modus) | Entwicklung und Debugging |
| cargo build --release | Kompiliere das Projekt (optimierter Modus) | Produktions-Release, aktiviert alle Compiler-Optimierungen |
| cargo run | Kompilieren und ausführen | Code schnell testen |
| cargo test | Tests ausführen | Unit-Tests, Integrationstests, Dokumentationstests |
| cargo check | Schnell prüfen, ob der Code kompiliert (ohne eine Binärdatei zu erzeugen) | Echtzeit-Syntaxprüfung in IDEs |
| cargo doc --open | Dokumentation generieren und öffnen | API-Dokumentation von Abhängigkeitsbibliotheken durchsuchen |
| cargo clippy | Code-Stil-Prüfung (Lint) | Nicht empfohlene Praktiken und potenzielle Probleme erkennen |
| cargo fmt | Code-Formatierung | Code-Stil vereinheitlichen |
| cargo publish | Auf crates.io veröffentlichen | Eine 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:
| Tool | Funktion | Ersetzt |
|---|---|---|
| ripgrep (rg) | Ultraschnelle Codesuche | grep |
| fd | Schnelle Dateisuche | finde |
| bat | Dateianzeige mit Syntaxhervorhebung | cat |
| Delta | Git-Diff mit Syntaxhervorhebung | git diff |
| zoxide | Intelligente Verzeichnisnavigation | cd |
| dust | Speicherplatzanalyse | du |
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
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
// 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
// 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.
| Kategorie | Type | Beispiel | Beschreibung |
|---|---|---|---|
| Vorzeichenbehaftete Ganzzahlen | i8, i16, i32, i64, i128, isize | let x: i32 = -42; | Der Standard-Ganzzahltyp ist i32 |
| Vorzeichenlose Ganzzahlen | u8, u16, u32, u64, u128, usize | let y: u64 = 100; | usize wird für Indizierung und Längen verwendet |
| Gleitkommazahlen | f32, f64 | let pi: f64 = 3.14; | Der Standard-Gleitkommatyp ist f64 |
| Boolean | bool | let ok: bool = true; | true oder false |
| Zeichen | char | let 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 Array | Vec<T> | let v: Vec<i32> = vec![1,2,3]; | Variable Länge, auf dem Heap allokiert |
| String | String / &str | let 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
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
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
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
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
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:
| Befehl | Funktion | Beschreibung |
|---|---|---|
| rustup update | Die Rust-Toolchain aktualisieren | rustc, cargo usw. auf die neueste stabile Version aktualisieren |
| rustup component add rustfmt | Code-Formatierungswerkzeug installieren | Code beim Speichern automatisch formatieren |
| rustup component add clippy | Code-Linting-Werkzeug installieren | Nicht empfohlene Codemuster und potenzielle Fehler finden |
| rustup doc | Lokale Dokumentation öffnen | Die Standardbibliothek und The Book offline anzeigen |
| cargo add <crate> | Eine Abhängigkeit hinzufügen | Cargo.toml automatisch bearbeiten und die neueste Version abrufen |
| cargo update | Abhängigkeitsversionen aktualisieren | Cargo.lock gemäß den Versionsbeschränkungen in Cargo.toml aktualisieren |
| cargo tree | Abhängigkeitsbaum anzeigen | Die vollständigen Abhängigkeitsbeziehungen des Projekts visualisieren |
| cargo bench | Benchmarks ausführen | Codeleistung messen |
| cargo install <tool> | CLI-Tools installieren, die in Rust geschrieben sind | z. 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
// 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:
| Dimension | Rusts Leistung |
|---|---|
| Lernkurve | Steil (das Ownership-System erfordert Verständnis und Anpassung), aber die Konzepte sind stabil; einmal gelernt, profitiert man ein Leben lang davon |
| Kompiliergeschwindigkeit | Relativ langsam (umfangreiche Prüfungen zur Kompilierzeit), aber durch cargo check und inkrementelle Kompilierung abgemildert |
| Laufzeit-Performance | Auf demselben Niveau wie C/C++, weit vor Sprachen mit GC |
| Speichersicherheit | Zur Kompilierzeit garantiert, kein GC nötig, keine Nullzeiger, hängenden Zeiger oder Pufferüberläufe |
| Nebenläufigkeitssicherheit | Beseitigt Datenrennen zur Kompilierungszeit, furchtlose Nebenläufigkeit |
| Reife des Ökosystems | Wächst rasant, bereits sehr ausgereift in Systemprogrammierung, WebAssembly und Blockchain |
| Am besten geeignet für | Systemprogrammierung, leistungsstarke Backends, WebAssembly, eingebettete Systeme, Blockchain, CLI-Tools, Datenbanken |
| Nicht sehr geeignet für | Schnelles Prototyping (Kompilierungszeit), GUI-Desktop-Anwendungen, Spieleentwicklung (im Vergleich zu ausgereiften Engines) |