Kapitel 1. Wahrheit oder Konsequenz
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
Und die Wahrheit ist, dass wir nichts wissen
They Might Be Giants, "Ana Ng" (1988)
In diesem Kapitel zeige ich dir, wie du ein Rust-Programm organisierst, ausführst und testest. Ich verwende eine Unix-Plattform (macOS), um einige grundlegende Ideen über Kommandozeilenprogramme zu erklären.Nur einige dieser Ideen gelten auch für das Windows-Betriebssystem, aber die Rust-Programme selbst funktionieren unabhängig von der Plattform, die du verwendest, gleich.
Du lernst, wie du die folgenden Dinge tun kannst:
-
Rust-Code in eine ausführbare Datei kompilieren
-
Verwende Cargo, um ein neues Projekt zu starten
-
Verwende die Umgebungsvariable
$PATH
-
Externe Rust Crates von crates.io einbinden
-
Den Exit-Status eines Programms interpretieren
-
Allgemeine Systembefehle und Optionen verwenden
-
Schreibe Rust-Versionen der Programme
true
undfalse
-
Organisiere, schreibe und führe Tests durch
Erste Schritte mit "Hallo, Welt!"
Es scheint der allgemein anerkannte Weg zu sein, eine Programmiersprache zu lernen, indem man "Hallo, Welt!" auf den Bildschirm druckt. Wechsle in ein temporäres Verzeichnis mit cd /tmp
um dieses erste Programm zu schreiben. Da wir nur herumspielen, brauchen wirnochkein richtigesVerzeichnis.Starte dann einen Texteditor und gib den folgenden Code in eine Datei namens hello.rs ein:
fn
main
(
)
{
println!
(
"
Hello, world!
"
)
;
}
Funktionen werden mit
fn
definiert. Der Name für diese Funktion lautetmain
.println!
(Zeile drucken) ist ein Makro und druckt den Text aufSTDOUT
(sprich: standard out). Das Semikolon zeigt das Ende der Anweisung an.Der Körper der Funktion ist in geschweifte Klammern eingeschlossen.
Rust startet automatisch in der Funktion main
. Funktionsargumente erscheinen innerhalb der Klammern, die dem Namen der Funktion folgen. Da in main()
keine Argumente aufgelistet sind, benötigt die Funktion keine Argumente. Als Letztes möchte ich noch darauf hinweisen, dass println!
wie eine Funktion aussieht, aber eigentlich ein Makro ist, also ein Code, der Code schreibt. Alle anderen Makros, die ich in diesem Buch verwende - wie z. B. assert!
und - enden ebenfalls mit einem Ausrufezeichen. vec!
- enden ebenfalls mit einem Ausrufezeichen.
Um dieses Programm auszuführen, musst du zuerst den Rust-Compiler rustc
verwenden, um den Code in eine Form zu kompilieren, die dein Computer ausführen kann:
$ rustc hello.rs
Unter Windows wirst du diesen Befehl verwenden:
> rustc.exe .\hello.rs
Wenn alles gut geht, gibt es keine Ausgabe des vorangegangenen Befehls, aber du solltest jetzt eine neue Datei namens hello unter macOS und Linux oder hello.exe unter Windows haben. Dies ist eine binär kodierte Datei, die direkt von deinem Betriebssystem ausgeführt werden kann. Unter macOS kannst du den Befehl file
verwenden, um zu sehen, um welche Art von Datei es sich handelt:
$ file hello hello: Mach-O 64-bit executable x86_64
Du solltest in der Lage sein, das Programm auszuführen, um eine charmante und zu Herzen gehende Nachricht zu sehen:
$ ./hello Hello, world!
Tipp
Ich werde kurz auf die Umgebungsvariable $PATH
eingehen, die die Verzeichnisse auflistet, in denen nach auszuführenden Programmen gesucht wird. Das aktuelle Arbeitsverzeichnis wird nie in diese Variable aufgenommen, um zu verhindern, dass bösartiger Code heimlich ausgeführt wird. Ein Bösewicht könnte zum Beispiel ein Programm namens ls
erstellen, das rm -rf /
ausführt und versucht, dein gesamtes Dateisystem zu löschen. Wenn du dieses Programm zufällig als Root-Benutzer ausführst, wäre dein ganzer Tag ruiniert.
Unter Windows kannst du sie wie folgt ausführen:
> .\hello.exe Hello, world!
Glückwunsch, wenn das dein erstes Rust-Programm war. Als nächstes zeige ich dir, wie du deinen Code besser organisieren kannst.
Organisieren eines Rust-Projektverzeichnisses
In deinen Rust-Projekten wirst du wahrscheinlich viele Quellcodedateien schreiben und auch den Code anderer Leute von Orten wie crates.io verwenden. Am besten legst du für jedes Projekt ein Verzeichnis mit einem src-Unterverzeichnis für die Rust-Quellcodedateien an.Auf einem Unix-System musst du zuerst das hello-Binary mit dem Befehl rm hello
entfernen, da dies der Name des Verzeichnisses ist, das du erstellen wirst.Dann kannst du mit dem folgenden Befehl die Verzeichnisstruktur erstellen:
$ mkdir -p hello/src
Mit dem Befehl
mkdir
wird ein Verzeichnis erstellt. Die Option-p
sagt, dass übergeordnete Verzeichnisse erstellt werden sollen, bevor untergeordnete Verzeichnisse erstellt werden. Die PowerShell benötigt diese Option nicht.
Verschiebe die Quelldatei hello.rs mit dem Befehl mv
nach hello/src:
$ mv hello.rs hello/src
Verwende den Befehl cd
, um in das Verzeichnis zu wechseln und dein Programm neu zu kompilieren:
$ cd hello $ rustc src/hello.rs
Du solltest jetzt eine ausführbare Datei hello
in dem Verzeichnis haben. Ich werde den Befehl tree
(den du eventuell installieren musst) verwenden, um dir den Inhalt meines Verzeichnisses zu zeigen:
$ tree . ├── hello └── src └── hello.rs
Dies ist die Grundstruktur für ein einfaches Rust-Projekt.
Erstellen und Ausführen eines Projekts mit Cargo
Eine einfachere Möglichkeit, ein neues Rust-Projekt zu starten, ist die Verwendung des Cargo-Tools. Du kannst dein temporäres hello-Verzeichnis löschen:
$ cd .. $ rm -rf hello
Wechsle in das übergeordnete Verzeichnis, das mit zwei Punkten gekennzeichnet ist (
..
).Die Option
-r
recursive entfernt den Inhalt eines Verzeichnisses und die Option-f
force überspringt alle Fehler.
Wenn du das folgende Programm speichern möchtest, wechsle in das Lösungsverzeichnis für deine Projekte. Dann starte dein Projekt neu mit Cargo wie folgt:
$ cargo new hello Created binary (application) `hello` package
Dadurch sollte ein neues Hallo-Verzeichnis erstellt werden, in das du wechseln kannst. Ich verwende wieder tree
, um dir den Inhalt zu zeigen:
$ cd hello $ tree . ├── Cargo.toml └── src └── main.rs
Cargo.toml ist eine Konfigurationsdatei für das Projekt. Die Erweiterung .toml steht für Tom's Obvious, Minimal Language.
Das Verzeichnis src ist für die Rust-Quellcode-Dateien.
main.rs ist der Standardstartpunkt für Rust-Programme.
Du kannst den folgenden Befehl cat
(für concatenate) verwenden, um den Inhalt der einen Quelldatei zu sehen, die Cargo erstellt hat (in Kapitel 3 wirst du eine Rust-Version von cat
schreiben):
$ cat src/main.rs fn main() { println!("Hello, world!"); }
Statt rustc
zum Kompilieren des Programms zu verwenden, benutze diesmal cargo run
um den Quellcode zu kompilieren und ihn in einem Befehl auszuführen:
$ cargo run Compiling hello v0.1.0 (/private/tmp/hello) Finished dev [unoptimized + debuginfo] target(s) in 1.26s Running `target/debug/hello` Hello, world!
Die ersten drei Zeilen enthalten Informationen darüber, was die Ladung macht.
Dies ist die Ausgabe des Programms.
Wenn du möchtest, dass Cargo keine Statusmeldungen über die Kompilierung und Ausführung des Codes ausgibt, kannst du die Option -q
oder --quiet
verwenden:
$ cargo run --quiet Hello, world!
Nachdem du das Programm mit Cargo ausgeführt hast, verwende den Befehl ls
, um den Inhalt des aktuellen Arbeitsverzeichnisses aufzulisten.(Du wirst in Kapitel 14 eine Rust-Version von ls
schreiben.) Es sollte ein neues Verzeichnis namens target geben. Standardmäßig erstellt Cargo ein Debug-Target, daher siehst du das Verzeichnis target/debug, das die Build-Artefakte enthält:
$ ls Cargo.lock Cargo.toml src/ target/
Du kannst den tree
-Befehl von vorhin oder den find
-Befehl verwenden (du wirst in Kapitel 7 eine Rust-Version von find
schreiben), um dir alle Dateien anzusehen, die Cargo und Rust erstellt haben.Die ausführbare Datei, die ausgeführt wurde, sollte als target/debug/hello existieren. Du kannst sie direkt ausführen:
$ ./target/debug/hello Hello, world!
Zusammenfassend lässt sich sagen, dass Cargo den Quellcode in src/main.rs gefunden hat, die Funktion main
verwendet hat, um die Binärdatei target/debug/hello zu erstellen, und sie dann ausgeführt hat. Warum heißt die Binärdatei aber hello und nicht main? Um das zu beantworten, schau dir Cargo.toml an:
$ cat Cargo.toml [package] name = "hello" version = "0.1.0" edition = "2021" # See more keys and their definitions at # https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies]
Dies war der Name des Projekts, das ich mit Cargo erstellt habe, also wird dies auch der Name der ausführbaren Datei sein.
Dies ist die Version des Programms.
Dies ist die Edition von Rust, die zum Kompilieren des Programms verwendet werden sollte. Mit den Editionen führt die Rust-Gemeinschaft Änderungen ein, die nicht abwärtskompatibel sind. Ich werde für alle Programme in diesem Buch die Edition 2021 verwenden.
Dies ist eine Kommentarzeile, die ich nur dieses eine Mal einfügen werde. Du kannst diese Zeile aus deiner Datei entfernen, wenn du möchtest.
Hier kannst du alle externen Kisten auflisten, die dein Projekt verwendet. In diesem Projekt gibt es noch keine, daher ist dieser Abschnitt leer.
Hinweis
Rust-Bibliotheken werden Crates genannt, und es wird erwartet, dass sie semantische Versionsnummern in der Form major.minor.patch
verwenden, so dass 1.2.4
die Hauptversion 1, die Nebenversion 2 und die Patch-Version 4 ist. Eine Änderung der Hauptversion bedeutet, dass die öffentliche Programmierschnittstelle (API) der Crate geändert wurde.
Schreiben und Ausführen von Integrationstests
"Mehr noch als das Testen ist das Entwerfen von Tests eine der besten bekannten Fehlervermeidungsmethoden. Die Überlegungen, die angestellt werden müssen, um einen nützlichen Test zu erstellen, könnenFehler aufdecken undbeseitigen, bevor sie programmiert werden. Tatsächlich kann das Denken beim Testentwurf Fehler in jeder Phase der Softwareentwicklung aufdeckenund beseitigen, von der Konzeption über die Spezifikation, das Design und die Programmierung bis hin zum Rest.
Boris Beizer, Software Testing Techniques (Van Nostrand Reinhold)
Auch wenn "Hallo, Welt!" recht einfach ist, gibt es dennoch Dinge, die getestet werden können. Es gibt zwei große Kategorien von Tests, die ich in diesem Buch vorstellen werde.Inside-out oder Unit-Tests sind Tests, die du für die Funktionen innerhalb deines Programms schreibst. Ich werde Unit-Tests in Kapitel 5 vorstellen.Outside-in oder Integrationstests sind Tests, die deine Programme so ausführen, wie es der Benutzer tun könnte, und das werden wir für dieses Programm tun. Die Konvention in Rust-Projekten ist es, ein Tests-Verzeichnis parallel zum src-Verzeichnis für den Testcode zu erstellen, und du kannst den Befehl mkdir tests
dafür verwenden.
Das Ziel ist es, das Programm hello
zu testen, indem du es auf der Kommandozeile so ausführst, wie es der Benutzer tun wird. Erstelle die Datei tests/cli.rs für die Kommandozeilenschnittstelle (CLI) mit dem folgenden Code.Beachte, dass diese Funktion den einfachsten möglichen Test in Rust darstellen soll, aber noch nichts Nützliches tut:
#[
test
]
fn
works
(
)
{
assert!
(
true
)
;
}
Das
#[test]
Attribut sagt Rust, dass diese Funktion beim Testen ausgeführt werden soll.Das Makro
assert!
behauptet, dass ein boolescher Ausdrucktrue
ist.
Dein Projekt sollte jetzt wie folgt aussehen:
$ tree -L 2 . ├── Cargo.lock ├── Cargo.toml ├── src │ └── main.rs ├── target │ ├── CACHEDIR.TAG │ ├── debug │ └── tmp └── tests └── cli.rs
In der DateiCargo.lock werden die genauen Versionen der Abhängigkeiten festgehalten, die für die Erstellung deines Programms verwendet werden. Du solltest diese Datei nicht bearbeiten.
Das src-Verzeichnis ist für die Rust-Quellcodedateien, mit denen das Programm erstellt wird.
Das Zielverzeichnis enthält die Build-Artefakte.
Das Verzeichnis tests enthält den Rust-Quellcode zum Testen des Programms.
Alle Tests in diesem Buch verwenden assert!
, um zu überprüfen, ob eine Erwartung true
ist, oder assert_eq!
um zu überprüfen, ob etwas ein erwarteter Wert ist. Da dieser Test den literalen Wert true
auswertet, wird er immer erfolgreich sein. Um diesen Test in Aktion zu sehen, führe cargo test
In der Ausgabe solltest du diese Zeilen sehen:
running 1 test test works ... ok
Um zu sehen, ob der Test fehlschlägt, ändere true
in false
in der Datei tests/cli.rs:
#[
test
]
fn
works
(
)
{
assert!
(
false
)
;
}
In der Ausgabe solltest du den folgenden fehlgeschlagenen Test sehen:
running 1 test test works ... FAILED
Tipp
Du kannst so viele assert!
und assert_eq!
Aufrufe in einer Testfunktion haben, wie du willst. Wenn einer von ihnen fehlschlägt, schlägt der gesamte Test fehl.
Jetzt wollen wir einen nützlicheren Test erstellen, der einen Befehl ausführt und das Ergebnis überprüft.Der Befehl ls
funktioniert sowohl unter Unix als auch unter Windows PowerShell, also fangen wir damit an. Ersetze den Inhalt von tests/cli.rs durch den folgenden Code:
use
std
::
process
::
Command
;
#[
test
]
fn
runs
(
)
{
let
mut
cmd
=
Command
::
new
(
"
ls
"
)
;
let
res
=
cmd
.
output
(
)
;
assert!
(
res
.
is_ok
(
)
)
;
}
Importieren
std::process::Command
. Diestd
sagt uns, dass dies in der Standardbibliothek ist und dass es sich um Rust-Code handelt, der so universell nützlich ist, dass er in der Sprache enthalten ist.Erstelle eine neue
Command
, umls
auszuführen. Mit dem Schlüsselwortlet
wird ein Wert an eine Variable gebunden. Das Schlüsselwortmut
macht diese Variable veränderbar, so dass sie sich ändern kann.Führen Sie den Befehl aus und erfassen Sie die Ausgabe, die ein
Result
.Überprüfe, ob das Ergebnis eine
Ok
Variante ist, die anzeigt, dass die Aktion erfolgreich war.
Tipp
Standardmäßig sind Rust-Variablen unveränderlich, das heißt, ihre Werte können nicht geändert werden.
Führen Sie aus cargo test
und überprüfe, ob du in der Ausgabe einen bestandenen Test siehst:
running 1 test test runs ... ok
Aktualisiere tests/cli.rs mit dem folgenden Code, damit die Funktion runs
hello
statt ls
ausführt:
use
std
::
process
::
Command
;
#[
test
]
fn
runs
(
)
{
let
mut
cmd
=
Command
::
new
(
"
hello
"
)
;
let
res
=
cmd
.
output
(
)
;
assert!
(
res
.
is_ok
(
)
)
;
}
Führe den Test erneut durch und stelle fest, dass er fehlschlägt, weil das Programm hello
nicht gefunden werden kann:
running 1 test test runs ... FAILED
Wenn du versuchst, hello
auf der Kommandozeile auszuführen, wirst du feststellen, dass das Programm nicht gefunden werden kann:
$ hello -bash: hello: command not found
Wenn du einen Befehl ausführst, sucht dein Betriebssystem in einer vordefinierten Reihe von Verzeichnissen nach etwas, das diesen Namen trägt.1
Auf Unix-Systemen kannst du die Umgebungsvariable PATH
deiner Shell einsehen, um diese Liste von Verzeichnissen zu sehen, die durch Doppelpunkte getrennt sind. (Unter Windows ist dies $env:Path
.) Ich kann tr
(Zeichen übersetzen) verwenden, um die Doppelpunkte (:
) durch Zeilenumbrüche (\n
) zu ersetzen, um dir mein PATH
zu zeigen:
$ echo $PATH | tr : '\n' /opt/homebrew/bin /Users/kyclark/.cargo/bin /Users/kyclark/.local/bin /usr/local/bin /usr/bin /bin /usr/sbin /sbin
$PATH
weistbash
an, die Variable zu interpolieren. Verwende eine Pipe (|
), um dies antr
weiterzuleiten.
Selbst wenn ich in das Ziel-/Debug-Verzeichnis wechsle, kann hello
aufgrund der bereits erwähnten Sicherheitseinschränkungen, die das aktuelle Arbeitsverzeichnis von meinem PATH
ausschließen, nicht gefunden werden:
$ cd target/debug/ $ hello -bash: hello: command not found
Ich muss explizit auf das aktuelle Arbeitsverzeichnis verweisen, damit das Programm läuft:
$ ./hello Hello, world!
Als Nächstes muss ich einen Weg finden, um Binärdateien auszuführen, die nur in der aktuellen Kiste existieren.
Hinzufügen von Projektabhängigkeiten
Derzeit existiert das Programm hello
nur im Verzeichnis target/debug.Wenn ich es in eines der Verzeichnisse in meinem PATH
kopiere (beachte, dass ich das Verzeichnis $HOME/.local/bin für private Programme mit einbeziehe), kann ich es ausführen und den Test erfolgreich durchführen. Ich möchte aber nicht mein Programm kopieren, um es zu testen, sondern das Programm testen, das sich in der aktuellen crate befindet.Ich kann die crate assert_cmd
verwenden, um das Programm in meinem crate-Verzeichnis zu finden.Ich werde auch die crate pretty_assertions
verwenden, um eine Version des assert_eq!
Makros zu benutzen, die Unterschiede zwischen zwei Strings besser anzeigt als die Standardversion.
Zuerst muss ich diese als Entwicklungsabhängigkeiten zur Cargo.toml hinzufügen.Das sagt Cargo, dass ich diese Kisten nur zum Testen und Benchmarking brauche:
[
package
]
name
=
"
hello
"
version
=
"
0.1.0
"
edition
=
"
2021
"
[
dependencies
]
[
dev-dependencies
]
assert_cmd
=
"
2.0.13
"
pretty_assertions
=
"
1.4.0
"
Ich kann dann assert_cmd
verwenden, um ein Command
zu erstellen, das in den Cargo-Binärverzeichnissen nachschaut.Der folgende Test überprüft nicht, ob das Programm die richtige Ausgabe produziert, sondern nur, ob es erfolgreich zu sein scheint.Aktualisiere deine tests/cli.rs mit dem folgenden Code, damit die Funktion runs
assert_cmd::Command
statt std::process::Command
verwendet:
use
assert_cmd
::
Command
;
#[
test
]
fn
runs
(
)
{
let
mut
cmd
=
Command
::
cargo_bin
(
"
hello
"
)
.
unwrap
(
)
;
cmd
.
assert
(
)
.
success
(
)
;
}
Importiere
assert_cmd::Command
.Erstelle ein
Command
, umhello
in der aktuellen Kiste auszuführen. Dies gibt einResult
zurück, und der Code ruftResult::unwrap
auf, weil die Binärdatei gefunden werden sollte. Wenn das nicht der Fall ist, löstunwrap
eine Panik aus und der Test schlägt fehl, was eine gute Sache ist.Verwende
Assert::success
um sicherzustellen, dass der Befehl erfolgreich war.
Hinweis
Ich werde in späteren Kapiteln mehr über den Typ Result
sagen. Für den Moment solltet ihr wissen, dass dies eine Möglichkeit ist, etwas zu modellieren, das gelingen oder fehlschlagen kann und für das es zwei mögliche Varianten gibt: Ok
bzw. Err
.
Führen Sie cargo test
erneut aus und überprüfe, ob du jetzt einen bestandenen Test siehst:
running 1 test test runs ... ok
Verstehen der Programmausstiegswerte
Was bedeutet es, wenn ein Programm erfolgreich ausgeführt wurde?Kommandozeilenprogramme sollten dem Betriebssystem einen endgültigen Exit-Status melden, um Erfolg oder Misserfolg anzuzeigen. Die POSIX-Standards (Portable Operating System Interface) schreiben vor, dass der Standard-Exit-Code 0 ist, um Erfolg zu signalisieren (d.h. keine Fehler), und andernfalls eine beliebige Zahl von 1 bis 255.Ich kann dir das anhand der Shell bash
und dem Befehl true
zeigen. Hier ist die Handbuchseite von man true
für die Version, die es unter macOS gibt:
TRUE(1) BSD General Commands Manual TRUE(1) NAME true -- Return true value. SYNOPSIS true DESCRIPTION The true utility always returns with exit code zero. SEE ALSO csh(1), sh(1), false(1) STANDARDS The true utility conforms to IEEE Std 1003.2-1992 (''POSIX.2''). BSD June 27, 1991 BSD
Wie in der Dokumentation beschrieben, tut dieses Programm nichts, außer den Exit-Code Null zurückzugeben.Wenn ich das Programm true
ausführe, gibt es keine Ausgabe, aber ich kann die Variable bash
$?
einsehen, um den Exit-Status des letzten Befehls zu sehen:
$ true $ echo $? 0
Der Befehl false
ist eine logische Konsequenz, da er immer mit einem Exit-Code ungleich Null beendet:
$ false $ echo $? 1
Von allen Programmen, die du in diesem Buch schreiben wirst, wird erwartet, dass sie bei normaler Beendigung Null zurückgeben und bei einem Fehler einen Wert ungleich Null. Um dies zu sehen, kannst du Versionen von true
und false
schreiben. Beginne damit, ein src/bin-Verzeichnis mit mkdir src/bin
und erstelle dann src/bin/true.rs mit dem folgenden Inhalt:
fn
main
(
)
{
std
::
process
::
exit
(
0
)
;
}
Verwende die Funktion
std::process::exit
, um das Programm mit dem Wert Null zu beenden.
Dein src-Verzeichnis sollte jetzt die folgende Struktur haben:
$ tree src/ src/ ├── bin │ └── true.rs └── main.rs
Führe das Programm aus und überprüfe den Exit-Wert manuell:
$ cargo run --quiet --bin true $ echo $? 0
Füge den folgenden Test zu tests/cli.rs hinzu, um sicherzustellen, dass er richtig funktioniert. Es ist egal, ob du ihn vor oder nach der bestehenden Funktion runs
hinzufügst:
#[test]
fn
true_ok
()
{
let
mut
cmd
=
Command
::cargo_bin
(
"true"
).
unwrap
();
cmd
.
assert
().
success
();
}
Wenn du cargo test
aufrufst, solltest du die Ergebnisse der beiden Tests sehen:
running 2 tests test true_ok ... ok test runs ... ok
Hinweis
Die Tests werden nicht unbedingt in der gleichen Reihenfolge ausgeführt, in der sie im Code deklariert sind. Das liegt daran, dass Rust eine sichere Sprache für das Schreiben von nebenläufigem Code ist, was bedeutet, dass der Code in mehreren Threads ausgeführt werden kann. Die Tests nutzen diese Parallelität, um viele Tests parallel auszuführen, sodass die Testergebnisse bei jeder Ausführung in einer anderen Reihenfolge erscheinen können. Das ist eine Funktion und kein Fehler. Wenn du die Tests in der richtigen Reihenfolge ausführen möchtest, kannst du sie in einem einzelnen Thread über cargo test -- --test-threads=1
.
Rust-Programme beenden sich standardmäßig mit dem Wert Null. Erinnere dich daran, dass src/main.rs nicht explizit std::process::exit
aufruft. Das bedeutet, dass das Programm true
überhaupt nichts tun kann. Wenn du sichergehen willst, ändere src/bin/true.rs wie folgt:
fn
main
()
{}
Führe die Testsuite aus und überprüfe, ob sie immer noch besteht. Als Nächstes schreiben wir eine Version des Programms false
mit dem folgenden Quellcode in src/bin/false.rs:
fn
main
(
)
{
std
::
process
::
exit
(
1
)
;
}
Überprüfe manuell, dass der Exit-Wert des Programms nicht Null ist:
$ cargo run --quiet --bin false $ echo $? 1
Füge dann diesen Test zu tests/cli.rs hinzu, um zu überprüfen, ob das Programm bei der Ausführung einen Fehler meldet:
#[
test
]
fn
false_not_ok
(
)
{
let
mut
cmd
=
Command
::
cargo_bin
(
"
false
"
)
.
unwrap
(
)
;
cmd
.
assert
(
)
.
failure
(
)
;
}
Verwende die Funktion
Assert::failure
, um sicherzustellen, dass der Befehl fehlgeschlagen ist.
Führe aus cargo test
um zu überprüfen, ob alle Programme wie erwartet funktionieren:
running 3 tests test runs ... ok test true_ok ... ok test false_not_ok ... ok
Eine andere Möglichkeit, das Programm false
zu schreiben, verwendet std::process::abort
. Ändere src/bin/false.rs wie folgt:
fn
main
()
{
std
::process
::abort
();
}
Führe die Testsuite erneut aus, um sicherzustellen, dass das Programm immer noch wie erwartet funktioniert.
Testen der Programmausgabe
Es ist zwar schön zu wissen, dass mein Programm hello
korrekt beendet wird, aber ich möchte auch sicherstellen, dass es die korrekte Ausgabe auf STDOUT
ausgibt.Ändere deine Funktion runs
in tests/cli.rs wiefolgt:
use
assert_cmd
::
Command
;
use
pretty_assertions
::
assert_eq
;
#[
test
]
fn
runs
(
)
{
let
mut
cmd
=
Command
::
cargo_bin
(
"
hello
"
)
.
unwrap
(
)
;
let
output
=
cmd
.
output
(
)
.
expect
(
"
fail
"
)
;
assert!
(
output
.
status
.
success
(
)
)
;
let
stdout
=
String
::
from_utf8
(
output
.
stdout
)
.
expect
(
"
invalid UTF-8
"
)
;
assert_eq!
(
stdout
,
"
Hello, world!
\n
"
)
;
}
Importiere das
pretty_assertions::assert_eq
Makro zum Vergleichen von Werten anstelle der Standardversion von Rust.Rufe auf
Command::output
um den Befehlhello
auszuführen. VerwendeResult::expect
um die Ausgabe des Befehls zu erhalten oder mit der Meldung "fehlgeschlagen" zu sterben.Konvertiere die Ausgabe des Programms in UTF-8, worauf ich in Kapitel 4 näher eingehen werde.
Vergleiche die Ausgabe des Programms mit einem erwarteten Wert. Beachte, dass hier die
pretty_assertions
Version des Makrosassert_eq
verwendet wird.
Führe die Tests durch und überprüfe, ob hello
tatsächlich richtig funktioniert. Als Nächstes änderst du src/main.rs, um weitere Ausrufezeichen hinzuzufügen:
fn
main
()
{
println!
(
"Hello, world!!!"
);
}
Führe die Tests erneut durch, um zu sehen, ob der Test fehlschlägt:
running 3 tests test true_ok ... ok test false_not_ok ... ok test runs ... FAILED failures: ---- runs stdout ---- thread runs panicked at tests/cli.rs:10:5: assertion failed: `(left == right)` Diff < left / right > : <Hello, world!!! >Hello, world!
Das vorstehende Testergebnis gibt sich große Mühe, dir zu zeigen, wie die erwartete Ausgabe (rechts) von der tatsächlichen Ausgabe (links) abweicht.Die Terminalausgabe enthält sogar roten und grünen Text und hervorgehobenen Text, der hier nicht wiedergegeben werden kann. Auch wenn es sich hier um ein triviales Programm handelt, hoffe ich, dass du den Wert einer automatischen Überprüfung aller Aspekte der Programme, die wir schreiben, erkennen kannst.
Exit-Werte machen Programme kompatibler
Die korrekte Meldung des Exit-Status ist ein Merkmal gut funktionierender Befehlszeilenprogramme.Der Exit-Wert ist wichtig, weil ein fehlgeschlagener Prozess in Verbindung mit einem anderen Prozess dazu führen sollte, dass die Kombination fehlschlägt. Zum Beispiel kann ich den logischen und Operator &&
in bash
verwenden, um die beiden Befehle true
und ls
zu verketten. Nur wenn der erste Prozess Erfolg meldet, wird der zweite Prozess ausgeführt:
$ true && ls Cargo.lock Cargo.toml src/ target/ tests/
Wenn ich stattdessen den Befehl false && ls
ausführe, schlägt der erste Prozess fehl und ls
wird nie ausgeführt.Außerdem ist der Exit-Status des gesamten Befehls ungleich Null:
$ false && ls $ echo $? 1
Wenn Kommandozeilenprogramme Fehler korrekt melden, sind sie mit anderen Programmen kompatibel. In Unix-Umgebungen ist es sehr üblich, viele kleine Befehle zu Ad-hoc-Programmen auf der Kommandozeile zu kombinieren. Wenn ein Programm auf einen Fehler stößt, diesen aber nicht an das Betriebssystem meldet, könnten die Ergebnisse falsch sein. Es ist viel besser, wenn ein Programm abbricht, damit die zugrunde liegenden Probleme behoben werden können.
Zusammenfassung
In diesem Kapitel hast du einige wichtige Ideen zum Organisieren eines Rust-Projekts und einige grundlegende Ideen zu Kommandozeilenprogrammen kennengelernt. Zur Wiederholung:
-
Der Rust-Compiler
rustc
kompiliert Rust-Quellcode in eine maschinenausführbare Datei unter Windows, macOS und Linux. -
Das Cargo-Tool erstellt ein neues Rust-Projekt und kompiliert, startet und testet den Code.
-
Standardmäßig,
cargo new
erstellt ein neues Rust-Programm, das "Hallo, Welt!" ausgibt. -
Kommandozeilen-Tools wie
ls
,cd
,mkdir
undrm
akzeptieren oft Kommandozeilen-Argumente wie Datei- oder Verzeichnisnamen sowie Optionen wie-f
oder-p
. -
POSIX-kompatible Programme sollten mit einem Wert von 0 beendet werden, um einen Erfolg anzuzeigen, und mit einem Wert zwischen 1 und 255, um einen Fehler anzuzeigen.
-
Du hast gelernt, Crate-Abhängigkeiten zu Cargo.toml hinzuzufügen und die Crates in deinem Code zu verwenden.
-
Du hast ein Verzeichnis tests erstellt, um den Testcode zu organisieren, und du hast
#[test]
verwendet, um Funktionen zu markieren, die als Tests ausgeführt werden sollen. -
Du hast gelernt, wie man den Exit-Status eines Programms prüft und wie man den auf
STDOUT
ausgegebenen Text überprüft. -
Du hast gelernt, wie du alternative Binärdateien in einem Cargo-Projekt schreibst, ausführst und testest, indem du Quellcodedateien im Verzeichnis src/bin erstellst.
-
Du hast deine Implementierungen der Programme
true
undfalse
zusammen mit Tests geschrieben, um zu überprüfen, ob sie wie erwartet erfolgreich sind oder fehlschlagen. Du hast gesehen, dass ein Rust-Programm standardmäßig mit dem Wert Null beendet wird und dass die Funktionstd::process::exit
verwendet werden kann, um es explizit mit einem bestimmten Code zu beenden. Außerdem kann die Funktionstd::process::abort
verwendet werden, um ein Programm mit einem Fehlercode ungleich Null zu beenden.
Im nächsten Kapitel zeige ich dir, wie du ein Programm schreibst, das Kommandozeilenargumente verwendet, um die Ausgabe zu verändern.
1 Shell-Aliase und Funktionen können auch wie Befehle ausgeführt werden, aber ich spreche an dieser Stelle nur davon, Programme zu finden, die ausgeführt werden sollen.
Get Befehlszeilen-Rost now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.