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.
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
.
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.
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.
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 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.";
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