Dlaczego Rust ma String
i str
? Jakie są różnice między String
i str
? Kiedy używa się String
zamiast str
i odwrotnie? Czy jeden z nich staje się przestarzały?
Dlaczego Rust ma String
i str
? Jakie są różnice między String
i str
? Kiedy używa się String
zamiast str
i odwrotnie? Czy jeden z nich staje się przestarzały?
Odpowiedzi:
String
jest dynamicznym typem łańcucha stosu, takim jak Vec
: użyj go, gdy chcesz posiadać lub zmodyfikować dane łańcucha.
str
to 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 str
najczęś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
"foo"
to &'static str
. Dane są zakodowane na stałe w pliku wykonywalnym i ładowane do pamięci podczas działania programu.String
: String
dereferences do &str
widoku z String
danych „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, String
jeś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, &str
jeś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ą T
a odniesieniem &T
dla typów ogólnych.
1 A str
ma 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 str
s 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ć &str
miejsce, 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.
[u8; N]
.
Rc<str>
i Arc<str>
można je teraz używać za pomocą standardowej biblioteki.
Mam C ++ tła i uważam, że to bardzo przydatna zastanowić String
i &str
pod względem c ++:
String
jest jak std::string
; jest właścicielem pamięci i wykonuje brudną robotę zarządzania pamięcią.&str
jest 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:
String
zachowuje bufor i jest bardzo praktyczny w użyciu. &str
jest 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.
&str
może zajrzeć do środka, String
ponieważ może wskazywać na dosłowny ciąg znaków. Poniższy kod musi skopiować literały ciąg do String
pamię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";
str
, używany tylko jako &str
, jest ciągiem znaków, odniesieniem do tablicy bajtów UTF-8.
String
to ~str
dawna, rosnąca, własna tablica bajtów UTF-8.
~str
teraz byłoBox<str>
~str
był uprawiany, podczas gdy Box<str>
nie był uprawiany. (To ~str
i ~[T]
były magicznie uprawiane, w przeciwieństwie do innych ~
obiektów, było dokładnie tego powodem String
i Vec<T>
zostały wprowadzone, aby wszystkie zasady były proste i spójne.)
W rzeczywistości są zupełnie inne. Po pierwsze, a str
jest 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 str
zajmuje, 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 str
jest koncepcyjnie tylko rzędem u8
bajtó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 &str
albo każdy inny wskaźnik do str
Like 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 &str
jest dość zbliżone do String
(ale nie do a &String
). A &str
to dwa słowa; jeden wskaźnik do pierwszego bajtu str
ai druga liczba, która opisuje, ile bajtów ma długość str
.
W przeciwieństwie do tego, co powiedziano, a str
nie musi być niezmienne. Jeśli możesz uzyskać wskaźnik &mut str
jako 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, &str
ale dodaje trzecie słowo, które jest pojemnością str
bufora na stercie, zawsze na stercie (a str
niekoniecznie jest na stercie), którą zarządza zanim zostanie wypełniona i będzie musiała ponownie przydzielić. String
zasadzie posiadastr
jak mówią; kontroluje to i może zmieniać jego rozmiar oraz ponownie przydzielać, kiedy uzna to za stosowne. Tak więc, String
jak powiedziano, bliżej &str
niż do str
.
Inną rzeczą jest Box<str>
; posiada również a, str
a jego środowisko wykonawcze jest takie samo jak a, &str
ale jest także właścicielem w str
przeciwieństwie do, &str
ale 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 String
jeś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 str
na poziomie typu służy głównie do tworzenia ogólnych abstrakcji &str
; istnieje na poziomie typu, aby móc wygodnie pisać cechy. Teoretycznie str
jako typ rzecz nie musiała istnieć i tylko to, &str
ale oznaczałoby to, że trzeba napisać dużo dodatkowego kodu, który może być teraz ogólny.
&str
jest bardzo przydatny, aby móc mieć wiele różnych podciągów String
bez konieczności kopiowania; jak powiedział String
posiadastr
na stercie zarządza, i jeśli można utworzyć tylko podciąg od a String
z nowym String
musiał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 str
tego samego łańcucha. string
jest właścicielem rzeczywistego pełnego str
buforu na stercie, a &str
podłańcuchy są po prostu wskaźnikami tłuszczu do tego buforu na stercie.
std::String
jest 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>,
}
str
jest 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 str
typ. test
jest odniesieniem do tego statycznie przydzielonego ciągu.
&str
nie można na przykład modyfikować
let mut word = "hello world";
word[0] = 's';
word.push('\n');
str
ma 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.
Krótko mówiąc, String
typ danych jest przechowywany na stercie (podobnie jak Vec
) i masz dostęp do tej lokalizacji.
&str
jest rodzajem plastra. Oznacza to, że jest to tylko odniesienie do już istniejącego String
gdzieś na stosie.
&str
nie dokonuje żadnej alokacji w czasie wykonywania. Tak więc ze względu na pamięć możesz użyć &str
ponad String
. Pamiętaj jednak, że podczas używania &str
możesz mieć do czynienia z jawnym życiem.
str
jest view
już obecne String
w kupie.
Dla osób C # i Java:
String
===StringBuilder
&str
Ciąg Rust === (niezmienny)Lubię myśleć o &str
widoku jako o łańcuchu, jak o internowanym łańcuchu w Javie / C #, w którym nie można go zmienić, wystarczy utworzyć nowy.
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 str
pożyczonym typem za pomocą widoku wycinka ciągu, takiego jak &str
.
Uwagi dotyczące użytkowania:
Preferuj, String
jeśli chcesz posiadać lub mutować ciąg - na przykład przekazując ciąg do innego wątku itp.
Preferuj, &str
jeśli chcesz mieć ciąg tylko do odczytu.
&str
składa się z dwóch komponentów: wskaźnik do niektórych bajtów, a długość”.