Jakie są różnice między „String” i „str” Rust'a?

418

Dlaczego Rust ma Stringi str? Jakie są różnice między Stringi str? Kiedy używa się Stringzamiast stri odwrotnie? Czy jeden z nich staje się przestarzały?

Daniel Fath
źródło

Odpowiedzi:

488

Stringjest dynamicznym typem łańcucha stosu, takim jak Vec: użyj go, gdy chcesz posiadać lub zmodyfikować dane łańcucha.

strto niezmienna 1 sekwencja bajtów UTF-8 o dynamicznej długości gdzieś w pamięci. Ponieważ rozmiar jest nieznany, można go obsługiwać tylko za wskaźnikiem. Oznacza to, że strnajczęściej 2 pojawia się jako &str: odniesienie do niektórych danych UTF-8, zwykle nazywanych „wycinkiem łańcucha” lub po prostu „wycinkiem”. Wycinek to tylko widok niektórych danych, które mogą znajdować się w dowolnym miejscu, np

  • W pamięci statycznej : literał łańcuchowy "foo"to &'static str. Dane są zakodowane na stałe w pliku wykonywalnym i ładowane do pamięci podczas działania programu.
  • Wewnątrz sterty przydzieloneString : Stringdereferences do &strwidoku z Stringdanych „s.
  • Na stosie : np. Następujące tworzy tablicę bajtów przydzieloną do stosu, a następnie wyświetla te dane jako&str :

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

Podsumowując, użyj, Stringjeśli potrzebujesz posiadanych danych ciągów (takich jak przekazywanie ciągów do innych wątków lub budowanie ich w czasie wykonywania), i użyj, &strjeśli potrzebujesz tylko widoku ciągu.

Jest to identyczne z relacją między wektorem Vec<T>a wycinkiem &[T]i jest podobne do relacji między wartością Ta odniesieniem &Tdla typów ogólnych.


1 A strma ustaloną długość; nie można pisać bajtów poza końcem ani pozostawiać końcowych niepoprawnych bajtów. Ponieważ UTF-8 jest kodowaniem o zmiennej szerokości, to skutecznie zmusza wszystkie strs do niezmienności w wielu przypadkach. Ogólnie rzecz biorąc, mutacja wymaga zapisania większej lub mniejszej liczby bajtów niż wcześniej (np. Zastąpienie a(1 bajtu) przez ä(2+ bajty) wymagałoby więcej miejsca w str). Istnieją określone metody, które mogą modyfikować &strmiejsce, głównie te, które obsługują tylko znaki ASCII, takie jak make_ascii_uppercase.

2 Typy o dynamicznym rozmiarze pozwalają np. Rc<str>Na sekwencję liczonych referencji bajtów UTF-8 od wersji Rust 1.2. Rdza 1.21 pozwala łatwo tworzyć te typy.

huon
źródło
10
„sekwencja bajtów UTF-8 ( o nieznanej długości )” - czy to jest nieaktualne? W docs powiedzieć „To &strskłada się z dwóch komponentów: wskaźnik do niektórych bajtów, a długość”.
mrec 10.10.16
11
To nie jest nieaktualne (że reprezentacja była dość stabilna), tylko trochę nieprecyzyjny: nie jest znany statycznie, w przeciwieństwie do, powiedzmy [u8; N].
huon
2
@mrec nie jest znane w czasie kompilacji, nie można przyjąć założeń dotyczących jego rozmiaru, na przykład podczas tworzenia ramki stosu. Dlatego często jest traktowany jako odwołanie, którego odwołanie jest znanym rozmiarem w czasie kompilacji, który jest rozmiarem wskaźnika.
Sekhat
1
Aktualizacja: Rc<str>i Arc<str>można je teraz używać za pomocą standardowej biblioteki.
Centril
1
@cjohansson Statycznie przydzielone obiekty zwykle nie są przechowywane ani na stercie, ani na stosie, ale we własnym regionie pamięci.
Brennan Vincent
96

Mam C ++ tła i uważam, że to bardzo przydatna zastanowić Stringi &strpod względem c ++:

  • Rdza Stringjest jak std::string; jest właścicielem pamięci i wykonuje brudną robotę zarządzania pamięcią.
  • Rdza &strjest jak char*(ale trochę bardziej wyrafinowana); wskazuje nam początek fragmentu w taki sam sposób, w jaki można uzyskać wskaźnik do zawartości std::string.

Czy któreś z nich zniknie? Nie sądzę. Służą one dwóm celom:

Stringzachowuje bufor i jest bardzo praktyczny w użyciu. &strjest lekki i powinien być używany do „przeglądania” ciągów znaków. Możesz wyszukiwać, dzielić, analizować, a nawet zamieniać porcje bez konieczności przydzielania nowej pamięci.

&strmoże zajrzeć do środka, Stringponieważ może wskazywać na dosłowny ciąg znaków. Poniższy kod musi skopiować literały ciąg do Stringpamięci zarządzanej:

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

Poniższy kod pozwala używać samego literału bez kopiowania (tylko do odczytu)

let a: &str = "hello rust";
Luis Ayuso
źródło
12
jak widok string?
Abhinav Gauniyal
1
Tak jak string_view, ale nieodłączny dla języka i poprawnie wypożyczyć zaznaczone.
locka
41

str, używany tylko jako &str, jest ciągiem znaków, odniesieniem do tablicy bajtów UTF-8.

Stringto ~strdawna, rosnąca, własna tablica bajtów UTF-8.

Chris Morgan
źródło
Technicznie rzecz biorąc, ~strteraz byłoBox<str>
jv110
3
@ jv110: nie, ponieważ ~strbył uprawiany, podczas gdy Box<str>nie był uprawiany. (To ~stri ~[T]były magicznie uprawiane, w przeciwieństwie do innych ~obiektów, było dokładnie tego powodem Stringi Vec<T>zostały wprowadzone, aby wszystkie zasady były proste i spójne.)
Chris Morgan
18

W rzeczywistości są zupełnie inne. Po pierwsze, a strjest niczym innym, jak tylko poziomem czcionki; można to uzasadnić tylko na poziomie typu, ponieważ jest to tak zwany typ dynamicznie wielkości (DST). Rozmiar, który strzajmuje, nie może być znany w czasie kompilacji i zależy od informacji o środowisku wykonawczym - nie można go zapisać w zmiennej, ponieważ kompilator musi wiedzieć w czasie kompilacji, jaki jest rozmiar każdej zmiennej. A strjest koncepcyjnie tylko rzędem u8bajtów z gwarancją, że tworzy poprawny UTF-8. Jak duży jest rząd? Nikt nie wie do czasu uruchomienia, dlatego nie można go zapisać w zmiennej.

Interesującą rzeczą jest to, że &stralbo każdy inny wskaźnik do strLike Box<str> nie istnieje w czasie wykonywania. Jest to tak zwany „wskaźnik tłuszczu”; jest wskaźnikiem z dodatkowymi informacjami (w tym przypadku wielkości rzeczy, na którą wskazuje), więc jest dwa razy większy. W rzeczywistości a &strjest dość zbliżone do String(ale nie do a &String). A &strto dwa słowa; jeden wskaźnik do pierwszego bajtu strai druga liczba, która opisuje, ile bajtów ma długość str.

W przeciwieństwie do tego, co powiedziano, a strnie musi być niezmienne. Jeśli możesz uzyskać wskaźnik &mut strjako wyłączny str, możesz go zmutować, a wszystkie bezpieczne funkcje, które go mutują, gwarantują utrzymanie ograniczenia UTF-8, ponieważ jeśli zostanie ono naruszone, wówczas nie zdefiniujemy zachowania, ponieważ biblioteka zakłada, że ​​to ograniczenie jest prawda i nie sprawdza jej.

Co to jest String? To trzy słowa; dwa są takie same jak dla, &strale dodaje trzecie słowo, które jest pojemnością strbufora na stercie, zawsze na stercie (a strniekoniecznie jest na stercie), którą zarządza zanim zostanie wypełniona i będzie musiała ponownie przydzielić. Stringzasadzie posiadastr jak mówią; kontroluje to i może zmieniać jego rozmiar oraz ponownie przydzielać, kiedy uzna to za stosowne. Tak więc, Stringjak powiedziano, bliżej &strniż do str.

Inną rzeczą jest Box<str>; posiada również a, stra jego środowisko wykonawcze jest takie samo jak a, &strale jest także właścicielem w strprzeciwieństwie do, &strale nie może zmienić jego rozmiaru, ponieważ nie zna swojej pojemności, więc w zasadzie Box<str>można postrzegać jako stałą długość String, której nie można zmienić ( zawsze zamień go na Stringjeśli chcesz zmienić jego rozmiar).

Istnieje bardzo podobny związek między [T]i Vec<T>chyba nie ma ograniczenia UTF-8 i może posiadać dowolny typ, którego rozmiar nie jest dynamiczny.

Użycie strna poziomie typu służy głównie do tworzenia ogólnych abstrakcji &str; istnieje na poziomie typu, aby móc wygodnie pisać cechy. Teoretycznie strjako typ rzecz nie musiała istnieć i tylko to, &strale oznaczałoby to, że trzeba napisać dużo dodatkowego kodu, który może być teraz ogólny.

&strjest bardzo przydatny, aby móc mieć wiele różnych podciągów Stringbez konieczności kopiowania; jak powiedział String posiadastr na stercie zarządza, i jeśli można utworzyć tylko podciąg od a Stringz nowym Stringmusiałby skopiowana, ponieważ wszystko w Rust może mieć tylko jeden pojedynczy właściciela do czynienia z bezpieczeństwem pamięci. Na przykład możesz pokroić ciąg:

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

Mamy dwa różne podciągi strtego samego łańcucha. stringjest właścicielem rzeczywistego pełnego strbuforu na stercie, a &strpodłańcuchy są po prostu wskaźnikami tłuszczu do tego buforu na stercie.

Zorf
źródło
4

std::Stringjest po prostu wektorem u8. Można znaleźć jego definicję w kodzie źródłowym . Jest alokowany na stos i można go uprawiać.

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

strjest prymitywnym typem, zwanym także wycinkiem łańcucha . Wycinek łańcucha ma ustalony rozmiar. Dosłowny ciąg typu let test = "hello world"ma &'static strtyp. testjest odniesieniem do tego statycznie przydzielonego ciągu. &strnie można na przykład modyfikować

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

strma zmienny plasterek &mut str, na przykład: 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);

Ale niewielka zmiana w UTF-8 może zmienić jego długość bajtu, a plasterek nie może ponownie przydzielić swojego odniesienia.

Aperion
źródło
0

Krótko mówiąc, Stringtyp danych jest przechowywany na stercie (podobnie jak Vec) i masz dostęp do tej lokalizacji.

&strjest rodzajem plastra. Oznacza to, że jest to tylko odniesienie do już istniejącego Stringgdzieś na stosie.

&strnie dokonuje żadnej alokacji w czasie wykonywania. Tak więc ze względu na pamięć możesz użyć &strponad String. Pamiętaj jednak, że podczas używania &strmożesz mieć do czynienia z jawnym życiem.

00imvj00
źródło
1
gdzieś w kupie - to nie jest do końca dokładne.
Shepmaster,
Miałem na myśli to, że strjest viewjuż obecne Stringw kupie.
00imvj00
1
Rozumiem, że o to ci chodziło i mówię, że nie jest to całkowicie dokładne. „Kupa” nie jest wymaganą częścią instrukcji.
Shepmaster,
-1

Dla osób C # i Java:

  • Rdza ” String===StringBuilder
  • &str Ciąg Rust === (niezmienny)

Lubię myśleć o &strwidoku jako o łańcuchu, jak o internowanym łańcuchu w Javie / C #, w którym nie można go zmienić, wystarczy utworzyć nowy.

Wiewiórka
źródło
1
Największą różnicą między ciągami Java / C # a ciągami Rust jest to, że Rust gwarantuje, że ciąg jest poprawnym unicode, ponieważ uzyskanie trzeciego znaku w ciągu wymaga więcej przemyślenia niż tylko „abc” [2]. (Biorąc pod uwagę, że żyjemy w wielojęzycznym świecie, jest to dobra rzecz.)
Wiewiórka
To jest błędne . Temat zmienności jest już poruszony w odpowiedzi, która została najczęściej głosowana; przeczytaj to, aby dowiedzieć się więcej.
Shepmaster
-5

Oto szybkie i łatwe wyjaśnienie.

String- Rosnąca, dostępna struktura danych przydzielana do sterty. Można go przymusić do &str.

str- jest (teraz, w miarę rozwoju Rust) zmiennym ciągiem o stałej długości, który żyje na stercie lub w pliku binarnym. Możesz wchodzić w interakcje tylko z strpożyczonym typem za pomocą widoku wycinka ciągu, takiego jak &str.

Uwagi dotyczące użytkowania:

Preferuj, Stringjeśli chcesz posiadać lub mutować ciąg - na przykład przekazując ciąg do innego wątku itp.

Preferuj, &strjeśli chcesz mieć ciąg tylko do odczytu.

Deweloper
źródło
To jest błędne . Temat zmienności jest już poruszony w odpowiedzi, która została najczęściej głosowana; przeczytaj to, aby dowiedzieć się więcej.
Shepmaster