Ferris Talk #6: Ein neuer Trick für die Formatstrings in Rust
source link: https://www.heise.de/hintergrund/Ferris-Talk-6-Ein-neuer-Trick-fuer-die-Formatstrings-in-Rust-6505377.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Ferris Talk #6: Ein neuer Trick für die Formatstrings in Rust
Rust 1.58 hat bei den Captured Identifiers etwas Neues gelernt – die Funktionserweiterung bietet Anlass, einen Blick auf die Formatstrings zu werfen.
Anzeige
Inhaltsverzeichnis
Das einfachste Programm, das man zum Einstieg in eine neue Programmiersprache schreibt, ist das berühmte "Hello World". In Rust sieht es so aus:
fn main() {
println!("Hello World!");
}
Listing 1: "Hello World"
Das println
-Kommando sticht sofort ins Auge. Tatsächlich handelt es sich um ein Makro, um genau zu sein, um ein Function-like Procedural Macro. Für Einsteiger und Einsteigerinnen ist die Tatsache, dass Rust schon für ein so einfaches Programm auf ein Makro zurückgreift, die erste Überraschung, die die Sprache bereithält. Wer bereits mehr mit Rust gemacht hat, weiß, dass das keine Ausnahme, sondern die Regel ist. Rust-Programme stecken in der Praxis voller Makros, die einem das Leben spürbar erleichtern. Wer Makros aus anderen Sprachen kennt und dort nicht liebgewonnen hat, den kann ich beruhigen: Makros in Rust sind ein mächtiges und stabiles Werkzeug.
Ferris Talks – die Kolumne für Rustaceans
Natürlich kann println
mehr als nur Textkonstanten auf stdout
ausgeben. Die wirkliche Power ergibt sich aus dem Formatstring, den das Makro entgegennimmt. Mit Rust 1.58 sind Formatstrings durch die neue Captured Identifiers-Funktion noch einmal kompakter und leichter lesbar geworden. Wir nehmen die aktuelle Spracherweiterung zum Anlass, um in dieser Ausgabe der Ferris Talks die Formatstrings in Rust genauer unter die Lupe zu nehmen und auch die erwähnten Captured Identifiers vorzustellen.
Das kleine Einmaleins von println & Co.
Rust bietet vier Basis-Makros zum Ausgeben auf stdout
und stderr
:
fn main() {
print!("Hello "); // Print without \n
println!("World!"); // Print with \n
eprint!("Oops, something "); // Print to stderr without \n
eprintln!("bad happened!"); // Print to stderr with \n
}
Listing 2: Ausgabe auf stdout und stderr
Will man Variablenwerte in die Ausgabe einbauen, baut man "Lücken" in den Formatstring ein. Das folgende Beispiel zeigt das Grundprinzip. Anschließend geht es um sämtliche Varianten, wie Formatstrings aufgebaut sein können.
fn main() {
// Print variable value
let answer = 42;
println!("The answer is {}", answer);
// Use format string to build string for further processing
// (here: convert answer string to base64 string).
let answer_base64 = base64::encode(format!("The answer is {}", answer));
println!("{}", answer_base64);
}
Listing 3: Platzhalter für Variablen im Formatstring
Das oben gezeigte Beispiel enthält neben println
ein weiteres, häufig verwendetes Makro: format
. Es ermöglicht das Verwenden von Formatstrings zum Zusammenbauen einer Ergebniszeichenkette im Hauptspeicher. Listing 3 baut mit format
einen Text zusammen, um ihn anschließend in Base64 zu encodieren.
Aufmerksame Leserinnen und Leser wundern sich, warum in der letzten Zeile von Listing 3 ein Formatstring zum Einsatz kam. Ist println!("{}", answer_base64);
notwendig oder ließe sich nicht einfach println!(answer_base64);
schreiben? Tatsächlich ist der Formatstring in diesem Fall verpflichtend, da Rust nur konstante Zeichenketten (String Literals) als Formatstrings akzeptiert. Der Grund dafür ist, dass Rust zugunsten der Performance und der Stabilität die Formatstrings nicht zur Laufzeit, sondern zur Kompilierzeit parst und prüft. Dynamisch zur Laufzeit erstellte Formatstrings sind in Rust nicht möglich. Das folgende Beispiel funktioniert daher nicht:
fn main() {
let answer = 42;
let format_string = "The answer is {}";
println!(format_string, answer); // This does NOT compile because
// format string needs to be
// a string literal.
}
Listing 4: Keine Variablen als Formatstrings
Zwei weitere Makros, die Formatstrings akzeptieren, sind write
und writeln
. Sie erstellen die Ergebniszeichenfolge und schreiben sie in einen Writer. Ein Writer ist in diesem Zusammenhang eine beliebige Instanz, die eine write_fmt
-Methode anbietet. In der Praxis kommt die Implementierung von write_fmt
meistens aus den Traits std::fmt::Write
oder std::io::Write
. Letzterer wird insbesondere von Strukturen implementiert, die Dateien, Streams und Ähnliches repräsentieren. Interessanterweise implementiert aber auch die Vec
-Struktur den std::io::Write
-Trait, wodurch folgender Code möglich wird:
use std::io::Write;
fn main() {
let answer = 42;
// facts will receive the UTF8 byte of the resulting string
let mut facts: Vec<u8> = Vec::new();
write!(&mut facts, "The answer is {}", answer).unwrap();
println!("{:?}", facts);
}
Listing 5: Formatstrings im write-Makro
Anzeige
Auch das panic
-Makro kann mit Formatstrings umgehen. Das ist in der Praxis häufig relevant, um sprechende Fehlermeldungen zusammenzubauen. Hier ein Beispiel mit einer Panic, deren Nachricht mit Formatstring generiert ist:
fn main() {
let answer = 41;
let expected_answer = 42;
if answer != expected_answer {
panic!("{} is not {} - the world is going TO END :-O", answer, expected_answer);
}
// The output (stderr) will be:
// thread 'main' panicked at '41 is not 42 - the world is going TO END :-O', src/main.rs:6:9
}
Listing 6: Formatstrings im panic-Makro
Die nächste Frage ist, wie die oben genannten Makros Variablenwerte in Text umwandeln. In vielen objektorientierten Programmiersprachen enthalten Objekte dafür eine virtuelle ToString
-Methode, die sich überschreiben lässt. Bei Rust ist das etwas anders, es sieht dafür primär zwei Traits vor:
- Eine Struktur, die sich in eine endbenutzertaugliche Textrepräsentation umwandeln lässt, muss explizit den
Display
-Trait implementieren. - Zum Umwandeln in eine technische Textrepräsentation (beispielsweise für Logs und zum Debuggen) muss eine Struktur den
Debug
-Trait implementieren. Dieser Trait lässt sich meist mit dem#[derive(Debug)]
-Makro generieren.
Um genau zu sein, gibt es auch in Rust im ToString
-Trait eine to_string
-Methode. Der Trait ist jedoch nicht direkt einzubetten, sondern der Fokus sollte auf Display
liegen. Strukturen, die Display
implementieren, implementieren automatisch auchToString
.
Das folgende Beispiel zeigt das Prinzip von Display
, Debug
und ToString
:
use std::fmt::{Display, Formatter, Result};
#[derive(Debug)] // Generate Debug trait impl using derive makro
struct Person {
first_name: String,
last_name: String,
}
impl Display for Person {
fn fmt(&self, f: &mut Formatter) -> Result {
// Use write macro to generate end-user friendly
// text representation of person instance.
write!(f, "{}, {}", self.last_name, self.first_name)
}
}
fn main() {
let p = Person {
first_name: String::from("Foo"),
last_name: String::from("Bar"),
};
// Print end-user friendly person
// Result: Bar, Foo
println!("{}", &p);
// Every struct that implements Display auto-implements
// the ToString trait. It offers the to_string function that
// turns the struct into a string.
println!("{}", p.to_string());
// Print technical string representation of person
// Result: Person { first_name: "Foo", last_name: "Bar" }
println!("{:?}", &p);
}
Listing 7: Display-, Debug- und ToString-Traits
In Listing 7 ist der Formatstring {:?}
zu beachten. Er führt dazu, dass println
den Debug
-Trait verwendet. Um exakt zu sein, signalisiert der Doppelpunkt, dass danach eine Formatangabe folgt. Das Fragezeichen in der Formatangabe ist es, das die Debug
-Ausgabe statt der endbenutzertauglichen Ausgabe auslöst. Das bringt uns zum nächsten Thema, der Ausgabeformatierung.
Seiten
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK