Каковы различия между `String` и` str` в Rust?

421

Почему у Rust есть Stringи str? Каковы различия между Stringи str? Когда один использует Stringвместо strи наоборот? Один из них становится устаревшим?

Даниэль Фат
источник

Ответы:

492

Stringэто динамический тип строки кучи, например Vec: используйте его, когда вам нужно владеть или изменять ваши строковые данные.

strявляется неизменной 1 последовательностью байтов UTF-8 динамической длины где-то в памяти. Поскольку размер неизвестен, его можно обрабатывать только за указателем. Это означает, что strчаще всего 2 выглядит как &str: ссылка на некоторые данные UTF-8, обычно называемые «фрагмент строки» или просто «фрагмент». Срез - это просто представление некоторых данных, и эти данные могут быть где угодно, например

  • В статическом хранилище : строковый литерал "foo"- это &'static str. Данные жестко закодированы в исполняемый файл и загружены в память при запуске программы.
  • Внутри кучи выделяютсяString : Stringразыменовывает на &strвзгляд из Stringданных «s.
  • В стеке : например, следующее создает выделенный стеком байтовый массив, а затем получает представление этих данных в виде&str :

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

Таким образом, используйте, Stringесли вам нужны собственные строковые данные (например, передавая строки в другие потоки или создавая их во время выполнения), и используйте, &strесли вам нужен только просмотр строки.

Это идентично взаимосвязи между вектором Vec<T>и срезом &[T]и аналогично взаимосвязи между побочным значением Tи побочной ссылкой &Tдля общих типов.


1 A str- фиксированная длина; Вы не можете писать байты за пределами конца или оставлять завершающие недопустимые байты. Поскольку UTF-8 является кодированием с переменной шириной, strво многих случаях это фактически заставляет все s быть неизменными. Как правило, мутация требует записи большего или меньшего количества байтов, чем было раньше (например, замена a(1 байт) на ä(2+ байта) потребует больше места в str). Существуют специальные методы, которые могут изменять на &strместе, в основном те, которые обрабатывают только символы ASCII, например make_ascii_uppercase.

2 Динамически изменяемые типы допускают такие вещи, как Rc<str>последовательность байтов UTF-8 с подсчетом ссылок начиная с Rust 1.2. Rust 1.21 позволяет легко создавать эти типы.

Юон
источник
10
«последовательность байтов UTF-8 ( неизвестной длины )» - это устарело? В документах говорят «А &strсостоит из двух компонентов: указатель на некоторые байты и длину.»
mrec
11
Это не устарело (это представление было довольно стабильным), просто немного неточно: оно не известно статически, в отличие, скажем, от [u8; N].
Хуон
2
@mrec это неизвестно во время компиляции, предположения о его размере не могут быть сделаны, например, при создании стекового фрейма. Таким образом, почему это часто рассматривается как ссылка, которая является известным размером во время компиляции, который является размером указателя.
Сехат
1
Обновление: Rc<str>и Arc<str>теперь можно использовать через стандартную библиотеку.
Centril
1
@cjohansson Статически размещенные объекты обычно хранятся не в куче и не в стеке, а в своей области памяти.
Бреннан Винсент
97

У меня есть опыт работы с C ++, и мне было очень полезно думать об этом Stringи &strв терминах C ++:

  • Ржавчина Stringпохожа на std::string; он владеет памятью и выполняет грязную работу по управлению памятью.
  • Ржавчина &strпохожа на char*(но немного более изощренна); он указывает нам на начало фрагмента так же, как вы можете получить указатель на содержимое std::string.

Кто-нибудь из них собирается исчезнуть? Я так не думаю. Они служат двум целям:

Stringсохраняет буфер и очень практичен в использовании. &strлегкий и должен использоваться для "просмотра" строк. Вы можете искать, разбивать, анализировать и даже заменять фрагменты без необходимости выделять новую память.

&strможет выглядеть внутри a, Stringпоскольку может указывать на какой-либо строковый литерал. Следующий код должен скопировать литеральную строку в Stringуправляемую память:

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

Следующий код позволяет использовать сам литерал без копирования (только для чтения)

let a: &str = "hello rust";
Луис Аюсо
источник
13
как string_view?
Абхинав Гауниял
2
Да, как string_view, но свойственный языку и правильно заимствованный проверен.
Лока
41

strиспользуется только как &strстроковый фрагмент, ссылка на байтовый массив UTF-8.

Stringэто то, что раньше было ~strрастущим, принадлежащим байтовым массивом UTF-8.

Крис Морган
источник
Технически, что раньше было ~strтеперьBox<str>
jv110
3
@ jv110: нет, потому что ~strбыл выращиваемым, а Box<str>не выращиваемым. (Это ~strи ~[T]было магически возможным, в отличие от любого другого ~объекта, именно поэтому Stringи Vec<T>было введено, так что все правила были простыми и последовательными.)
Крис Морган
18

Они на самом деле совершенно разные. Во-первых, это strне что иное, как вещь уровня типа; это можно рассуждать только на уровне типа, потому что это так называемый тип с динамическим размером (DST). Размер, который strтребуется принять, не может быть известен во время компиляции и зависит от информации времени выполнения - он не может быть сохранен в переменной, потому что компилятору необходимо знать во время компиляции, каков размер каждой переменной. strКонцептуально A представляет собой просто ряд u8байтов с гарантией того, что он образует действительный UTF-8. Насколько большой ряд? Никто не знает до времени выполнения, следовательно, он не может быть сохранен в переменной.

Интересно то, что &strи любой другой указатель на strLike Box<str> делает EXIST во время выполнения. Это так называемый «жирный указатель»; это указатель с дополнительной информацией (в данном случае размером с объект, на который он указывает), поэтому он в два раза больше. На самом деле, a &strдовольно близко к String(но не к &String). А &strдва слова; один указатель на первый байт a strи другое число, которое описывает, сколько байтов имеет длину str.

Вопреки сказанному, a strне обязательно должен быть неизменным. Если вы можете получить &mut strкак эксклюзивный указатель на str, вы можете изменить его, и все безопасные функции, которые изменяют его, гарантируют, что ограничение UTF-8 будет поддержано, потому что, если оно нарушается, мы имеем неопределенное поведение, поскольку библиотека предполагает, что это ограничение правда и не проверяет это.

Так что же это String? Это три слова; два - то же самое, что и для, &strно он добавляет третье слово, которое является емкостью strбуфера в куче, всегда в куче (а strне обязательно в куче), которой он управляет до того, как заполнится и должен перераспределить. Stringв основном владеетstr , как они говорят; он контролирует его и может изменить его размер и перераспределить, когда сочтет нужным. Так что String, как сказано, ближе к, &strчем к str.

Другое дело, это Box<str>; он также владеет a, strи его представление во время выполнения такое же, как a, &strно он также владеет strнепохожим, &strно не может изменить его размер, потому что не знает своей емкости, поэтому в основном a Box<str>можно рассматривать как фиксированную длину String, размер которой нельзя изменить (вы можете всегда конвертируйте его в a, Stringесли вы хотите изменить его размер).

Очень похожее соотношение существует между [T]и за Vec<T>исключением того, что нет UTF-8 , ограничения и он может содержать любой тип, размер которого не является динамическим.

Использование strна уровне типов в основном для создания общих абстракций с &str; он существует на уровне типов, чтобы можно было удобно писать черты. Теоретически, strкак тип, вещь не должна существовать и только, &strно это будет означать, что нужно будет написать много дополнительного кода, который теперь может быть универсальным.

&strсупер полезно иметь возможность иметь несколько разных подстрок a Stringбез необходимости копирования; как сказал String владеетstr на куче она управляет , и если вы можете создать только подстроку Stringс новым Stringон должен копироваться , потому что все в ржавчине может иметь только один единственный владелец , чтобы иметь дело с безопасностью памяти. Так, например, вы можете нарезать строку:

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

У нас есть две разные подстроки strодной и той же строки. stringэто тот, который владеет фактическим полным strбуфером в куче, а &strподстроки являются просто жирными указателями на этот буфер в куче.

Zorf
источник
4

std::Stringэто просто вектор u8. Вы можете найти его определение в исходном коде . Он выделен кучей и может расти.

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

strявляется примитивным типом, также называемым строковым срезом . Срез строки имеет фиксированный размер. Литеральная строка типа like let test = "hello world"имеет &'static strтип. testявляется ссылкой на эту статически размещенную строку. &strнельзя изменить, например,

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

strимеет изменяемый фрагмент &mut str, например: 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);

Но небольшое изменение в UTF-8 может изменить длину его байта, и срез не может перераспределить свой референт.

Aperion
источник
0

Проще говоря, Stringтип данных хранится в куче (точно так же как Vec), и у вас есть доступ к этому местоположению.

&strтип среза Это означает, что это просто ссылка на уже присутствующий Stringгде-то в куче.

&strне делает никакого выделения во время выполнения. Так, по причинам памяти, вы можете использовать &strболее String. Но имейте в виду, что при использовании &strвам, возможно, придется иметь дело с явными временами жизни.

00imvj00
источник
1
где-то в куче - это не совсем точно.
Шепмастер
Я имел в виду, что strон viewуже присутствует Stringв куче.
00imvj00
1
Я понимаю, что вы имели в виду, и я говорю, что это не совсем точно. «Куча» не является обязательной частью утверждения.
Шепмастер
-1

Для людей на C # и Java:

  • Rust ' String===StringBuilder
  • Руст &str === (неизменяемая) строка

Мне нравится думать о &strкак о взгляде на строку, как о интернированной строке в Java / C #, где вы не можете изменить ее, только создать новую.

белочка
источник
1
Самое большое различие между строками Java / C # и строками Rust заключается в том, что Rust гарантирует, что строка будет правильной в Юникоде, так как для получения третьего символа в строке требуется больше мысли, чем просто «abc» [2]. (Учитывая, что мы живем в многоязычном мире, это хорошо.)
Белка
Это неверно . Тема изменчивости уже рассматривается в ответе с наибольшим количеством голосов; Пожалуйста, прочитайте это, чтобы узнать больше.
Шепмейстер
-5

Вот быстрое и простое объяснение.

String- Растущая, доступная структура данных, выделенная кучей. Это может быть приведено к &str.

str- это (теперь, по мере развития Rust) изменяемая строка фиксированной длины, которая живет в куче или в двоичном файле. Вы можете взаимодействовать с strзаимствованным типом только через представление среза строки, например &str.

Особенности использования:

Предпочитаете, Stringесли хотите владеть или изменять строку - например, передать строку в другой поток и т. Д.

Предпочитаю, &strесли вы хотите, чтобы строка была доступна только для чтения.

разработчик
источник
Это неверно . Тема изменчивости уже рассматривается в ответе с наибольшим количеством голосов; Пожалуйста, прочитайте это, чтобы узнать больше.
Шепмейстер