Rust-lernen.de

Zeichenketten in Rust

Letzte Änderung: 09.11.2024

Rust bietet für den Umgang mit Zeichenketten zwei Datentypen: String und &str. Den Unterschied und wann man welchen Typ verwendet, sehen wir uns im Folgenden genauer an.

Zeichenketten in Rust

Was ist String?

Der Datentyp String beinhaltet eine Zeichenkette, die zur Laufzeit verändert werden kann. Die Zeichenkette wird dabei stets auf dem Haldenspeicher (heap) abgelegt. Nachfolgend ein Codebeispiel, das einen String erzeugt und diesen verändert:

let mut s = String::from("Hallo");
println!("{s}");  // Ausgabe: Hallo
s.push_str(" Welt");
println!("{s}");  // Ausgabe: Hallo Welt

Technisch gesehen handelt es sich bei String um eine Hülle um einen Vec<u8> mit UTF-8-Zeichenkodierung. In der Java-Welt entspricht das am ehesten der Klasse StringBuilder.

Was ist &str?

Der Typ &str ist ein Zeiger auf eine Zeichenkette oder einen Ausschnitt davon. Er beinhaltet also nur einen Zeiger (auf das erste Zeichen) und eine Längenangabe. Dabei spielt es keine Rolle, ob die referenzierte Zeichenkette auf dem Haldenspeicher oder im Programmspeicher liegt.

Nachfolgend ein Beispiel, bei dem die Zeichenkette im Programmspeicher liegt:

let s: &str = "Hallo Welt";
println!("{s}");  // Ausgabe: Hallo Welt

Die Variable s zeigt dabei auf die Speicheradresse im Programmspeicher, an der die Zeichenkette abgelegt ist.

Sehen wir uns als Nächstes ein Beispiel mit einer Zeichenkette auf dem Haldenspeicher an:

let s = String::from("Hallo Welt");
let s1: &str = &s;
println!("{s1}");  // Ausgabe: Hallo Welt

Zunächst erzeugen wir mittels String::from() eine neue Zeichenkette auf dem Haldenspeicher. Danach lassen wir s1 auf diese Zeichenkette zeigen.

Eine Variable vom Typ &str kann auch auf einen Ausschnitt einer Zeichenkette zeigen:

let s = String::from("Hallo Welt");
let world: &str = &s[6..10];
println!("{world}");  // Ausgabe: Welt

Das Speicherabbild der Variablen s und world sieht dann wie folgt aus:

Der Typ &str wird als Zeichenkettenanteilstyp (string slice) bezeichnet. Im Unterschied zu String referenziert &str keine aneigenbaren Werte (owned values), d.h. der referenzierte Wert wird am Ende des Gültigkeitsbereichs (scope) der &str-Variable nicht automatisch aufgeräumt.

&str wird typischerweise für rein lesende Zugriffe auf Zeichenketten verwendet und führt damit zu sehr effizientem Code.

Umwandlung zwischen String und &str

Ein Wert vom Typ String lässt sich recht einfach in den Typ &str umwandeln, ohne eine Methode aufrufen zu müssen:

let string_value = String::from("Hallo");
let str_value: &str = &string_value;

Da wir den Typ &str der Variable str_value explizit angegeben haben, konvertiert Rust den Typ automatisch. Dies ist bei Funktionsaufrufen mit &str-Parametern recht elegant und sehr effizient, da lediglich ein Zeiger und keine Kopie der Zeichenkette erstellt wird. Diese automatische Umwandlung (engl. deref coercion) funktioniert aber nur deshalb, weil der Typ String das Merkmal (trait) Deref implementiert. Mit expliziter Typ-Konvertierung sieht der Code so aus:

let string_value = String::from("Hallo");
let str_value = string_value.as_str();

Will man hingegen einen Wert vom Typ &str in den Typ String umwandeln, kann man folgendes schreiben:

let str_value = "Hallo";
let string_value = String::from(str_value);

Alternativ zum Aufruf String::from(str_value) kann man auch str_value.to_string() oder str_value.to_owned() verwenden. Diese Varianten sind alle gleichwertig und legen immer eine Kopie der Zeichenkette an.

Welchen Zeichenkettentyp soll ich verwenden?

Steve Klabnik empfiehlt in seinem Blogartikel folgende Regel:

„Verwende immer String in Strukturen (structs) und &str für Funktionsparameter. Wenn der Rückgabetyp deiner Funktion von einem Argument abgeleitet ist und im Funktionsrumpf nicht geändert wird, gib &str zurück. Wenn du hier auf Probleme stößt, gib stattdessen String zurück.“

Zeichenkettenliterale

Zeichenkettenliterale sind Zeichenketten, die man direkt in den Programmcode schreibt. Einfache Beispiele haben wir oben schon gesehen, wie etwa:

let s = "Hallo Welt";

Zeichenkettenliterale können auch Unicode-Zeichen enthalten:

let s = "Ich mag Rust 🦀❤️";

Will man Zeichen mit besonderer Syntax-Bedeutung in einem Zeichenkettenliteral verwenden, kann man diese entweder einzeln mit \ escapen oder rohe Zeichenketten (raw strings) verwenden:

let s = "Er sagte \"Zeig mir Rust\"";
let s = r#"Er sagte "Zeig mir Rust""#;

Auch mehrzeilige Zeichenketten inklusive Zeilenumbruch (\n) lassen sich in Rust problemlos schreiben:

let s = "Dies ist eine
Zeichenkette, die über
mehrere Zeilen geht.";

Nützliche Zeichenketten-Funktionen in Rust

Nachfolgend werden einige nützliche Zeichenketten-Funktionen aufgelistet, die Rust bereits mitbringt.

s.len()                    // Länge in Bytes
s.chars().count()          // Länge in Zeichen
s.is_empty()               // Ist s leer?
s.contains("We")           // Enthält s die angegebene Zeichenfolge?
s.starts_with("Hallo")     // Beginnt s mit der angegebenen Zeichenfolge?
s.ends_with("Welt")        // Endet s mit der angegebenen Zeichenfolge?
s.repeat(3)                // Wiederholt die Zeichenkette
s.find("Welt")             // Gibt Some(6) mit der Position von "Welt" zurück
s.replace("Welt", "Rust")  // Ersetzt Teile der Zeichenkette, gibt "Hallo Rust" zurück
s.trim()                   // Entfernt Leerzeichen (und andere unsichtbare Zeichen) am Anfang und Ende

Quellen