Каковы различия между `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подстроки являются просто жирными указателями на этот буфер в куче.


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 может изменить длину его байта, и срез не может перераспределить свой референт.


0

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

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

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


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если вы хотите, чтобы строка была доступна только для чтения.


Это неверно . Тема изменчивости уже рассматривается в ответе с наибольшим количеством голосов; Пожалуйста, прочитайте это, чтобы узнать больше.
— Шепмейстер
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.