4

Ferris Talk #6: Ein neuer Trick für die Formatstrings in Rust

 2 years ago
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.
neoserver,ios ssh client

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.

25.02.2022

08:08 Uhr

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.

Rust Meetup Linz, Rainer Stropek: "Interlude – A Love Letter To Rust"

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

Auf einer Seite lesen

comments_outline_white Kommentare lesen (4)

Zur Startseite


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK