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

418

Warum hat Rust Stringund str? Was sind die Unterschiede zwischen Stringund str? Wann verwendet man Stringstatt strund umgekehrt? Wird einer von ihnen veraltet?

Daniel Fath
quelle

Antworten:

488

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

strist eine unveränderliche 1- Sequenz von UTF-8-Bytes mit dynamischer Länge irgendwo im Speicher. Da die Größe unbekannt ist, kann man sie nur hinter einem Zeiger behandeln. Dies bedeutet, dass 2str am häufigsten als Verweis auf einige UTF-8-Daten angezeigt wird , die normalerweise als "String-Slice" oder nur als "Slice" bezeichnet werden. Ein Slice ist nur eine Ansicht einiger Daten, und diese Daten können sich überall befinden, z&str

  • Im statischen Speicher : Ein String-Literal "foo"ist a &'static str. Die Daten werden fest in die ausführbare Datei codiert und beim Ausführen des Programms in den Speicher geladen.
  • Innerhalb eines zugewiesenen HeapsString : StringDereferenzen zu einer &strAnsicht der StringDaten des Daten.
  • Auf dem Stapel : z. B. erstellt das Folgende ein vom Stapel zugewiesenes Byte-Array und erhält dann eine Ansicht dieser Daten als&str :

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

Zusammenfassend lässt sich sagen, Stringob Sie eigene Zeichenfolgendaten benötigen (z. B. Zeichenfolgen an andere Threads übergeben oder zur Laufzeit erstellen) und verwenden, &strwenn Sie nur eine Ansicht einer Zeichenfolge benötigen.

Dies ist identisch mit der Beziehung zwischen einem Vektor Vec<T>und einem Slice &[T]und ähnelt der Beziehung zwischen By-Value Tund By-Reference &Tfür allgemeine Typen.


1 A strist feste Länge; Sie können keine Bytes über das Ende hinaus schreiben oder nachfolgende ungültige Bytes belassen. Da UTF-8 eine Codierung mit variabler Breite ist, werden alle strs in vielen Fällen unveränderlich. Im Allgemeinen erfordert die Mutation das Schreiben von mehr oder weniger Bytes als zuvor (z. B. würde das Ersetzen eines a(1 Bytes) durch ein ä(2+ Bytes) mehr Platz im str) erfordern . Es gibt bestimmte Methoden, mit denen ein &strOrt geändert werden kann , hauptsächlich solche, die nur ASCII-Zeichen verarbeiten, wie zmake_ascii_uppercase .

2 Typen mit dynamischer Größe ermöglichen beispielsweise Rc<str>eine Folge von UTF-8-Bytes mit Referenzzählung seit Rust 1.2. Mit Rust 1.21 können diese Typen einfach erstellt werden.

huon
quelle
10
"Sequenz von UTF-8-Bytes ( unbekannter Länge )" - ist dies veraltet? In den Dokumenten heißt es: "A &strbesteht aus zwei Komponenten: einem Zeiger auf einige Bytes und einer Länge."
mrec
11
Es ist nicht veraltet (diese Darstellung war ziemlich stabil), nur ein wenig ungenau: Es ist statisch nicht bekannt, anders als beispielsweise [u8; N].
Huon
2
@mrec Es ist zur Kompilierungszeit unbekannt. Annahmen über die Größe können beispielsweise beim Erstellen eines Stapelrahmens nicht getroffen werden. Daher wird es häufig als Referenz behandelt, wobei eine Referenz zur Kompilierungszeit eine bekannte Größe hat, dh die Größe eines Zeigers.
Sekhat
1
Update: Rc<str>und können Arc<str>jetzt über die Standardbibliothek verwendet werden.
Centril
1
@cjohansson Statisch zugewiesene Objekte werden normalerweise weder auf dem Heap noch auf dem Stapel gespeichert, sondern in ihrem eigenen Speicherbereich.
Brennan Vincent
96

Ich habe einen C ++ Hintergrund und ich fand es sehr nützlich , um darüber nachzudenken Stringund &strin C ++ Bedingungen:

  • Ein Rost Stringist wie einstd::string ; Es besitzt den Speicher und erledigt die schmutzige Arbeit der Speicherverwaltung.
  • Ein Rost &strist wie ein char*(aber etwas raffinierter); Es zeigt uns auf den Anfang eines Blocks auf die gleiche Weise, wie Sie einen Zeiger auf den Inhalt von erhalten können std::string.

Wird einer von ihnen verschwinden? Ich glaube nicht. Sie dienen zwei Zwecken:

String Hält den Puffer und ist sehr praktisch zu bedienen. &strist leicht und sollte verwendet werden, um in Saiten zu "schauen". Sie können Chunks suchen, teilen, analysieren und sogar ersetzen, ohne neuen Speicher zuweisen zu müssen.

&strkann in ein schauen, Stringda es auf ein String-Literal verweisen kann. Der folgende Code muss die Literalzeichenfolge in den Stringverwalteten Speicher kopieren :

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

Mit dem folgenden Code können Sie das Literal selbst ohne Kopie verwenden (schreibgeschützt).

let a: &str = "hello rust";
Luis Ayuso
quelle
12
wie eine string_view?
Abhinav Gauniyal
1
Ja wie string_view, aber der Sprache eigen und ordnungsgemäß ausgeliehen.
Locka
41

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

Stringwar früher ~strein wachsbares UTF-8-Byte-Array.

Chris Morgan
quelle
Technisch gesehen war das, was früher war, ~strjetztBox<str>
jv110
3
@ jv110: nein, weil ~stres anbaubar war, während Box<str>es nicht anbaubar ist. (Das ~strund ~[T]waren magisch anbaubar, im Gegensatz zu jedem anderen ~Objekt, war genau der Grund Stringund Vec<T>wurden eingeführt, so dass die Regeln alle einfach und konsistent waren.)
Chris Morgan
18

Sie sind eigentlich ganz anders. Zunächst einmal strist a nichts anderes als eine Sache auf Typebene; Dies kann nur auf Typebene begründet werden, da es sich um einen sogenannten Typ mit dynamischer Größe (DST) handelt. Die Größe der strAufnahme kann zur Kompilierungszeit nicht bekannt sein und hängt von den Laufzeitinformationen ab. Sie kann nicht in einer Variablen gespeichert werden, da der Compiler zur Kompilierungszeit wissen muss, wie groß jede Variable ist. A strist konzeptionell nur eine Reihe von u8Bytes mit der Garantie, dass es gültiges UTF-8 bildet. Wie groß ist die Reihe? Niemand weiß es bis zur Laufzeit, daher kann es nicht in einer Variablen gespeichert werden.

Das Interessante daran ist , dass ein &stroder andere Zeiger auf einen strwie Box<str> tun exist zur Laufzeit. Dies ist ein sogenannter "Fettzeiger"; Es ist ein Zeiger mit zusätzlichen Informationen (in diesem Fall die Größe des Objekts, auf das es zeigt), also doppelt so groß. In der Tat ist a &strziemlich nah an a String(aber nicht an a &String). A &strist zwei Wörter; Ein Zeiger auf das erste Byte von a strund eine andere Zahl, die beschreibt, wie viele Bytes das strist.

Im Gegensatz zu dem, was gesagt wird, strmuss a nicht unveränderlich sein. Wenn Sie einen &mut strals exklusiven Zeiger auf das erhalten können str, können Sie ihn mutieren, und alle sicheren Funktionen, die ihn mutieren, garantieren, dass die UTF-8-Einschränkung beibehalten wird. Wenn dies verletzt wird, haben wir ein undefiniertes Verhalten, da die Bibliothek diese Einschränkung annimmt wahr und prüft nicht darauf.

Was ist ein String? Das sind drei Wörter; zwei sind die gleichen wie für, &straber es wird ein drittes Wort hinzugefügt, das die Kapazität des strPuffers auf dem Heap ist, immer auf dem Heap (a strist nicht unbedingt auf dem Heap), den er verwaltet, bevor er gefüllt wird und neu zugewiesen werden muss. das besitzt im StringGrunde ein wie sie sagen; Es steuert es und kann seine Größe ändern und es neu zuweisen, wenn es dies für richtig hält. Also ist a wie gesagt näher an a als an a .strString&strstr

Eine andere Sache ist a Box<str>; Dies besitzt auch ein strund seine Laufzeitdarstellung ist das gleiche wie ein, &straber es besitzt auch das andere strals das, &straber es kann seine Größe nicht ändern, da es seine Kapazität nicht kennt, so dass a im Grunde genommen Box<str>als eine feste Länge angesehen werden Stringkann, deren Größe nicht geändert werden kann (Sie können konvertieren Sie es immer in ein, Stringwenn Sie die Größe ändern möchten).

Eine sehr ähnliche Beziehung besteht zwischen [T]und Vec<T>außer es gibt keine UTF-8-Einschränkung und sie kann jeden Typ enthalten, dessen Größe nicht dynamisch ist.

Die Verwendung strauf Typebene dient hauptsächlich dazu, generische Abstraktionen mit zu erstellen &str. Es existiert auf Typebene, um Merkmale bequem schreiben zu können. Theoretisch musste es strals Typ nicht existieren und nur, &straber das würde bedeuten, dass viel zusätzlicher Code geschrieben werden müsste, der jetzt generisch sein kann.

&strist sehr nützlich, um mehrere verschiedene Teilzeichenfolgen von a haben zu können, Stringohne kopieren zu müssen; Wie gesagt, a String besitzt das strauf dem Heap, das es verwaltet, und wenn Sie nur einen Teilstring von a Stringmit einem neuen erstellen könnten, Stringmüsste dieser kopiert werden, da alles in Rust nur einen einzigen Eigentümer haben kann, der sich mit der Speichersicherheit befasst. So können Sie beispielsweise eine Zeichenfolge in Scheiben schneiden:

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

Wir haben zwei verschiedene Teilzeichenfolgen strderselben Zeichenfolge. stringist derjenige, der den tatsächlichen vollen strPuffer auf dem Heap besitzt und die &strTeilzeichenfolgen sind nur fette Zeiger auf diesen Puffer auf dem Heap.

Zorf
quelle
4

std::Stringist einfach ein Vektor von u8. Sie finden die Definition im Quellcode . Es ist haufenweise zugewiesen und kann angebaut werden.

#[derive(PartialOrd, Eq, Ord)]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct String {
    vec: Vec<u8>,
}

str ist ein primitiver Typ, auch genannt String Slice genannt . Ein String-Slice hat eine feste Größe. Eine Literalzeichenfolge wie let test = "hello world"hat &'static strTyp. testist ein Verweis auf diese statisch zugewiesene Zeichenfolge. &strkann zum Beispiel nicht geändert werden

let mut word = "hello world";
word[0] = 's';
word.push('\n');

strhat veränderbare Schicht &mut str, zum Beispiel: pub fn split_at_mut(&mut self, mid: usize) -> (&mut str, &mut str)

let mut s = "Per Martin-Löf".to_string();
{
    let (first, last) = s.split_at_mut(3);
    first.make_ascii_uppercase();
    assert_eq!("PER", first);
    assert_eq!(" Martin-Löf", last);
}
assert_eq!("PER Martin-Löf", s);

Eine kleine Änderung an UTF-8 kann jedoch die Bytelänge ändern, und ein Slice kann seinen Referenten nicht neu zuweisen.

Aperion
quelle
0

In einfachen Worten, Stringist der Datentyp auf dem Heap gespeichert (genau wie Vec), und Sie haben Zugriff auf diesen Speicherort.

&strist ein Slice-Typ. Das heißt, es ist nur ein Hinweis auf eine bereits Stringirgendwo auf dem Haufen vorhandene.

&strführt zur Laufzeit keine Zuordnung durch. Aus Speichergründen können Sie also &strover verwenden String. Beachten Sie jedoch, dass Sie sich bei der Verwendung &strmöglicherweise mit expliziten Lebensdauern auseinandersetzen müssen.

00imvj00
quelle
1
irgendwo auf dem Haufen - das ist nicht ganz richtig.
Shepmaster
Was ich meinte war, dass strdas viewschon Stringim Haufen vorhanden ist.
00imvj00
1
Ich verstehe, dass Sie das gemeint haben, und ich sage, dass das nicht ganz richtig ist. Der "Heap" ist kein erforderlicher Teil der Anweisung.
Shepmaster
-1

Für C # - und Java-Benutzer:

  • Rust ' String===StringBuilder
  • Rusts &str === (unveränderlicher) String

Ich stelle mir eine &strals Ansicht einer Zeichenfolge vor, wie eine internierte Zeichenfolge in Java / C #, bei der Sie sie nicht ändern können, sondern nur eine neue erstellen.

Eichhörnchen
quelle
1
Der größte Unterschied zwischen Java / C # -Strings und Rust-Strings besteht darin, dass Rust garantiert, dass der String ein korrekter Unicode ist. Daher erfordert das Abrufen des dritten Zeichens in einem String mehr Gedanken als nur "abc" [2]. (Angesichts der Tatsache, dass wir in einer mehrsprachigen Welt leben, ist dies eine gute Sache.)
Eichhörnchen
Das ist falsch . Das Thema Veränderlichkeit wird bereits in der Antwort mit den höchsten Stimmen angesprochen. Bitte lesen Sie es, um mehr zu erfahren.
Shepmaster
-5

Hier ist eine schnelle und einfache Erklärung.

String- Eine erweiterbare, besitzbare Heap-zugewiesene Datenstruktur. Es kann zu a gezwungen werden &str.

str- ist (jetzt, da sich Rust weiterentwickelt) eine veränderbare Zeichenfolge mit fester Länge, die auf dem Heap oder in der Binärdatei lebt. Sie können nur strals ausgeliehener Typ über eine String-Slice-Ansicht interagieren , z&str .

Überlegungen zur Verwendung:

Ziehen StringSie es vor, wenn Sie eine Zeichenfolge besitzen oder mutieren möchten, z. B. die Zeichenfolge an einen anderen Thread übergeben usw.

Bevorzugen &strSie, wenn Sie eine schreibgeschützte Ansicht einer Zeichenfolge haben möchten.

Entwickler
quelle
Das ist falsch . Das Thema Veränderlichkeit wird bereits in der Antwort mit den höchsten Stimmen angesprochen. Bitte lesen Sie es, um mehr zu erfahren.
Shepmaster