1018 Stimmen

Was sind die Unterschiede zwischen Rusts `String` und `str`?

Warum hat Rust sowohl String als auch str? Was sind die Unterschiede zwischen ihnen und wann sollte man eine gegenüber der anderen verwenden? Wird eine von ihnen veraltet?

1148voto

huon Punkte 84005

String ist der dynamische Zeichenketten-Typ auf dem Heap, ähnlich wie Vec: Verwenden Sie ihn, wenn Sie Ihre Zeichendaten besitzen oder ändern müssen.

str ist eine unveränderliche1 Sequenz von UTF-8-Bytes variabler Länge an irgendeinem Speicherort im Speicher. Da die Größe unbekannt ist, kann man sie nur hinter einem Zeiger verarbeiten. Das bedeutet, dass str am häufigsten2 als &str erscheint: ein Verweis auf einige UTF-8-Daten, normalerweise als "Zeichenfolgenslice" oder einfach als "Slice" bezeichnet. Ein Slice ist nur eine Ansicht auf einige Daten, und diese Daten können überall sein, z. B.

  • Im statischen Speicher: Ein Zeichenfolgenliteral "foo" ist ein 'static str. Die Daten sind im ausführbaren Code fest codiert und werden beim Ausführen des Programms in den Speicher geladen.

  • In einem auf dem Heap allokierten String: String verweist auf eine &str-Ansicht der Daten des String.

  • Auf dem Stack: z. B. erstellt der folgende Code ein auf dem Stack angelegtes Byte-Array und ruft dann eine Ansicht dieser Daten als &str ab:

    use std::str;
    
    let x: [u8; 3] = [b'a', b'b', b'c'];
    let stack_str: &str = str::from_utf8(&x).unwrap();

Zusammenfassend verwenden Sie String, wenn Sie im Besitz von Zeichendaten sein müssen (z. B. bei der Weitergabe von Zeichenfolgen an andere Threads oder beim Erstellen von ihnen zur Laufzeit) und verwenden Sie &str, wenn Sie nur eine Ansicht einer Zeichenfolge benötigen.

Dies entspricht der Beziehung zwischen einem Vektor Vec und einem Slice &[T] und ist ähnlich der Beziehung zwischen by-value T und by-reference &T für allgemeine Typen.


1 Ein str ist fester Länge; Sie können keine Bytes hinter das Ende schreiben oder ungültige Bytes am Ende lassen. Da UTF-8 eine variable Breite hat, zwingt dies in vielen Fällen alle str dazu, unveränderlich zu sein. Im Allgemeinen erfordert eine Mutation das Schreiben von mehr oder weniger Bytes als zuvor (z. B. die Ersetzung eines a (1 Byte) durch ein ä (2+ Bytes) würde erfordern, mehr Platz im str zu schaffen). Es gibt spezifische Methoden, die ein &mut str vor Ort ändern können, hauptsächlich solche, die nur ASCII-Zeichen behandeln, wie make_ascii_uppercase.

2 Dynamisch dimensionierte Typen ermöglichen Dinge wie Rc für eine Sequenz von referenzzählte UTF-8-Bytes seit Rust 1.2. Rust 1.21 ermöglicht das einfache Erstellen dieser Typen.

263voto

Luis Ayuso Punkte 3107

Ich habe einen C++-Hintergrund und fand es sehr nützlich, über String und &str in C++-Begriffen nachzudenken:

  • Ein Rust String ist wie ein std::string; es besitzt den Speicher und übernimmt den lästigen Teil der Speicherverwaltung.
  • Ein Rust &str ist wie ein char* (aber etwas ausgefeilter); es zeigt uns auf den Anfang eines Abschnitts in ähnlicher Weise wie man einen Zeiger auf den Inhalt von std::string erhalten kann.

Werden sie irgendwann verschwinden? Ich glaube nicht. Sie erfüllen zwei Zwecke:

String behält den Puffer und ist sehr praktisch zu verwenden. &str ist leichtgewichtig und sollte verwendet werden, um in Strings "hineinzuschauen". Sie können suchen, teilen, analysieren und sogar Abschnitte ersetzen, ohne neuen Speicher zu allozieren.

&str kann in einen String hineinschauen, da es auf einen Stringliteral zeigen kann. Der folgende Code muss den literalen String in den vom String verwalteten Speicher kopieren:

let a: String = "hello rust".into();

Der folgende Code ermöglicht es Ihnen, das Literal selbst ohne Kopie zu verwenden (jedoch nur lesen):

let a: &str = "hello rust";

73voto

Chris Morgan Punkte 78929

str, nur als &str verwendet, ist ein Zeichenslice, ein Verweis auf ein UTF-8-Byte-Array.

String ist das, was früher ~str war, ein vergrößerbares, im Besitz befindliches UTF-8-Byte-Array.

71voto

Zorf Punkte 5860

Sie sind tatsächlich völlig verschieden. Zunächst ist ein str nichts anderes als eine Typ-Level-Sache; sie kann nur auf Typ-Ebene betrachtet werden, weil es sich um einen sogenannten dynamisch dimensionierten Typ (DST) handelt. Die Größe, die der str einnimmt, kann zur Kompilierzeit nicht bekannt sein und hängt von Laufzeitinformationen ab — sie kann nicht in einer Variable gespeichert werden, weil der Compiler zur Kompilierzeit die Größe jeder Variablen kennen muss. Ein str ist konzeptionell nur eine Reihe von u8-Bytes mit der Garantie, dass sie gültiges UTF-8 bilden. Wie groß ist die Reihe? Niemand weiß es, bis zur Laufzeit kann sie daher nicht in einer Variable gespeichert werden.

Das Interessante ist, dass ein &str oder ein anderer Zeiger auf einen str wie Box tatsächlich zur Laufzeit existieren. Dies wird als "fat pointer" bezeichnet; es ist ein Zeiger mit zusätzlichen Informationen (in diesem Fall die Größe des Dinges, auf das er zeigt), deshalb ist er doppelt so groß. Tatsächlich ist ein &str ziemlich nah an einem String (aber nicht an einem &String). Ein &str besteht aus zwei Wörtern; ein Zeiger auf das erste Byte eines str und eine weitere Zahl, die angibt, wie viele Bytes der str lang ist.

Anders als behauptet, muss ein str nicht unveränderlich sein. Wenn Sie einen &mut str als exklusiven Zeiger auf den str erhalten, können Sie ihn verändern und alle sicheren Funktionen, die ihn verändern, garantieren, dass die UTF-8-Bedingung eingehalten wird, denn wenn diese verletzt wird, haben wir ein undefiniertes Verhalten, da die Bibliothek davon ausgeht, dass diese Bedingung wahr ist und sie nicht überprüft.

Was ist also ein String? Das sind drei Wörter; zwei sind die gleichen wie für &str, aber es fügt ein drittes Wort hinzu, das die Kapazität des auf dem Heap befindlichen str-Puffers angibt, der immer auf dem Heap liegt (ein str muss nicht unbedingt auf dem Heap sein), den sie verwaltet, bevor er gefüllt ist und neu zugewiesen werden muss. Der String besitzt im Grunde genommen einen str, wie sie sagen; er kontrolliert ihn und kann ihn vergrößern und neu zuweisen, wenn er es für angemessen hält. Ein String ist, wie gesagt, näher an einem &str als an einem str.

Eine weitere Sache ist eine Box; diese besitzt ebenfalls einen str und ihre Laufzeit-Repräsentation ist die gleiche wie für ein &str, aber sie besitzt auch den str im Unterschied zum &str, kann ihn aber nicht vergrößern, weil sie seine Kapazität nicht kennt. Im Grunde kann man eine Box als festlängigen String betrachten, der nicht vergrößert werden kann (man kann ihn immer in einen String umwandeln, wenn man ihn vergrößern möchte).

Eine sehr ähnliche Beziehung besteht zwischen [T] und Vec, es gibt jedoch keine UTF-8-Bedingung und sie kann jedes Typ halten, dessen Größe nicht dynamisch ist.

Die Verwendung von str auf Typ-Ebene dient hauptsächlich dazu, generische Abstraktionen mit &str zu erstellen; sie existiert auf Typ-Ebene, um Traits bequem schreiben zu können. Theoretisch hätte ein str als Typ-Objekt nicht existieren müssen, sondern nur ein &str, aber das würde bedeuten, dass viel zusätzlicher Code geschrieben werden müsste, der jetzt generisch sein kann.

&str ist sehr nützlich, um mehrere unterschiedliche Teilstrings eines String zu haben, ohne kopieren zu müssen; wie gesagt, ein String besitzt den auf dem Heap verwalteten str, und wenn Sie nur einen Teilstring eines String mit einem neuen String erstellen könnten, müsste er kopiert werden, weil alles in Rust nur einen einzigen Besitzer haben kann, um mit der Speichersicherheit umzugehen. So können Sie z.B. einen String aufschneiden:

let string: String   = "a string".to_string();
let substring1: &str = &string[1..3];
let substring2: &str = &string[2..4];

Wir haben zwei verschiedene Unterstring-strs desselben Strings. string ist derjenige, der den tatsächlichen gesamten str-Puffer auf dem Heap besitzt, und die Unterstring-&strs sind nur Fat-Pointer zu diesem Puffer auf dem Heap.

37voto

Willem van der Veen Punkte 26043

Rost &str und String


String:

  • Rosts besitzender String-Typ, der String selbst lebt auf dem Heap und ist daher veränderlich und kann Größe und Inhalt ändern.
  • Weil String im Besitz ist, wenn die Variablen, die den String besitzen, den Gültigkeitsbereich verlassen, wird der Speicher auf dem Heap freigegeben.
  • Variablen vom Typ String sind fat pointers (Zeiger + zugehörige Metadaten)
  • Der fat pointer ist 3 * 8 Bytes (Wordsize) lang und besteht aus den folgenden 3 Elementen:
    • Zeiger auf tatsächliche Daten auf dem Heap, er zeigt auf das erste Zeichen
    • Länge des Strings (# Zeichen)
    • Kapazität des Strings auf dem Heap

&str:

  • Rosts nicht besitzender String-Typ, ist standardmäßig unveränderlich. Der String selbst befindet sich irgendwo anders im Speicher, normalerweise auf dem Heap oder im 'static-Speicher.
  • Weil String nicht im Besitz ist, wird der Speicher des Strings nicht freigegeben, wenn &str-Variablen den Gültigkeitsbereich verlassen.
  • Variablen vom Typ &str sind fat pointers (Zeiger + zugehörige Metadaten)
  • Der fat pointer ist 2 * 8 Bytes (Wordsize) lang und besteht aus den folgenden 2 Elementen:
    • Zeiger auf tatsächliche Daten auf dem Heap, er zeigt auf das erste Zeichen
    • Länge des Strings (# Zeichen)

Beispiel:

use std::mem;

fn main() {
    // auf 64-Bit-Architektur:
    println!("{}", mem::size_of::<&str>()); // 16
    println!("{}", mem::size_of::()); // 24

    let string1: &'static str = "abc";
    // string zeigt auf 'static memory, der während des gesamten Programms bestehen bleibt

    let ptr = string1.as_ptr();
    let len = string1.len();

    println!("{}, {}", unsafe { *ptr as char }, len); // a, 3
    // Länge beträgt 3 Zeichen, also 3
    // Zeiger auf das erste Zeichen zeigt auf Buchstabe a

    {
        let mut string2: String = "def".to_string();

        let ptr = string2.as_ptr();
        let len = string2.len();
        let capacity = string2.capacity();
        println!("{}, {}, {}", unsafe { *ptr as char }, len, capacity); // d, 3, 3
        // Zeiger auf das erste Zeichen zeigt auf Buchstabe d
        // Länge beträgt 3 Zeichen, also 3
        // String hat jetzt 3 Bytes Speicher auf dem Heap

        string2.push_str("ghijk"); // wir können den String-Typ verändern, Kapazität und Länge ändern sich ebenfalls
        println!("{}, {}", string2, string2.capacity()); // defghijk, 8

    } // Speicher von string2 auf dem Heap wird hier freigegeben, da Besitzer den Gültigkeitsbereich verlässt

}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X