У чому полягають відмінності між Рустовою `String` та` str`?


418

Чому іржа Stringі str? Які відмінності між Stringі str? Коли використовується один, Stringа не strнавпаки? Хтось із них стає застарілим?

Відповіді:


489

String- це тип динамічного рядка купи, наприклад Vec: використовуйте його, коли потрібно володіти або змінювати ваші рядкові дані.

strє незмінною 1 послідовністю байт UTF-8 динамічної довжини десь у пам'яті. Оскільки розмір невідомий, обробляти його можна лише за вказівником. Це означає, що strнайчастіше 2 виглядає як &str: посилання на деякі дані UTF-8, зазвичай їх називають "рядковим фрагментом" або просто "фрагментом". Фрагмент - це лише перегляд деяких даних, і ці дані можуть бути де завгодно, наприклад

  • У статичному сховищі : літеральний рядок "foo"- це a &'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 А str- фіксованої довжини; ви не можете писати байтів поза межами кінця або залишати недійсні байти. Оскільки UTF-8 є кодуванням змінної ширини, це ефективно примушує всі strs бути непорушними у багатьох випадках. Взагалі мутація вимагає записати більше або менше байтів, ніж раніше (наприклад, заміна a(1 байт) на ä(2+ байтів) вимагала б більше місця в str). Існують конкретні методи, які можуть змінювати &strмісце, в основному ті, що працюють лише з символами ASCII make_ascii_uppercase.

2 Типи з динамічним розміром дозволяють на зразок Rc<str>послідовності посилань рахувати UTF-8 байт з Rust 1.2. Іржа 1,21 дозволяє легко створювати ці типи.


10
"послідовність байтів UTF-8 ( невідомої довжини )" - це застаріло? У документах кажуть «А &strскладається з двох компонентів: покажчик на деякі байти і довжину.»
mrec

11
Це не з - за дня (що подання було досить стабільним), тільки трохи неточне: статично відомо, в відміну від, скажімо, [u8; N].
хун

2
@mrec це невідомо під час компіляції, припущення про його розмір неможливо робити, наприклад, при створенні кадру стека. Тому часто його трактують як посилання, яке посилання є відомим розміром під час компіляції, який є розміром вказівника.
Сехат

1
Оновлення: Rc<str>і Arc<str>тепер можна використовувати з допомогою стандартної бібліотеки.
Сентриль

1
@cjohansson Статично виділені об'єкти зазвичай не зберігаються ні в купі, ні в стеку, а у власному регіоні пам'яті.
Вінсент

96

У мене є C ++, і мені було дуже корисно подумати Stringі про &strC ++:

  • Іржа Stringяк ніби std::string; вона володіє пам'яттю і виконує брудну роботу з управління пам'яттю.
  • Іржа &strяк ніби char*(але трохи складніша); це вказує нам на початок фрагменту так само, як ви можете отримати вказівник на вміст std::string.

Чи хтось із них зникне? Я так не думаю. Вони виконують дві цілі:

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

&strможе заглянути всередину а, Stringяк це може вказувати на деякий буквальний рядок. Наступний код повинен скопіювати буквальний рядок в Stringкеровану пам'ять:

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

Наступний код дозволяє використовувати літерал без копії (хоча читати)

let a: &str = "hello rust";

12
як string_view?
Абхінав Гауніял

1
Так, як string_view, але властива мові та належним чином запозичена перевірена.
locka

41

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

String- це те, що раніше було ~str, байтовим масивом UTF-8, що займається можливістю використання .


Технічно, що раніше було ~strтеперBox<str>
jv110

3
@ jv110: ні, тому що ~strбуло вирощувати, а Box<str>не вирощувати. (Це ~strі ~[T]було магічно вирощуваним, на відміну від будь-якого іншого ~-об'єкта, саме тому Stringі Vec<T>було запроваджено, так що всі правила були чіткими та послідовними.)
Кріс Морган,

18

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

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

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

Отже, що таке String? Це три слова; два - це те саме, що і для, &strале це додає третє слово - це ємність strбуфера на купі, завжди на купі (a strне обов'язково на купі), якою вона управляє, перш ніж її заповнити і доведеться перерозподілити. в Stringосновному володіє а, strяк кажуть; він контролює його і може змінити його розмір і перерозподілити його, коли вважає за потрібне. Тож а String, як сказано, ближче &strдо а str.

Інша справа - це Box<str>; цьому також належить a strі його представлення часу виконання таке ж, як, &strале воно також володіє на strвідміну від нього, &strале воно не може змінити його розмір, оскільки він не знає його ємності, а в основному a Box<str>може розглядатися як фіксована довжина, Stringяку неможливо змінити (ви можете завжди конвертуйте його у формат, 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це примітивний тип, який також називають струнним фрагментом . Струнковий фрагмент має фіксований розмір. Буквальний рядок типу 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:

  • Іржа ' String===StringBuilder
  • Рюст &str === (незмінний) рядок

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


1
Найбільша різниця між рядками Java / C # та рядками Rust полягає в тому, що Руст гарантує, що рядок є правильним unicode, тому що отримання третього charactor в рядку вимагає більше роздумів, ніж просто "abc" [2]. (З огляду на те, що ми живемо у багатомовному світі, це добре.)
Білка

Це неправильно . Тема мінливості вже розглянута у відповіді, що голосує; будь ласка, прочитайте його, щоб дізнатися більше.
Shepmaster

-5

Ось швидке та просте пояснення.

String- Зростаюча, власна структура даних, що виділяється нагромадженням. Його можна примусити до а &str.

str- це (зараз, як Руст розвивається) змінний рядок фіксованої довжини, що живе на купі або у двійковій. Ви можете взаємодіяти з strтипом, запозиченим, лише через вигляд фрагмента рядка, наприклад &str.

Принципи використання:

Віддайте перевагу, Stringякщо ви хочете володіти або мутувати рядок - наприклад, передача рядка в інший потік тощо.

Віддайте перевагу, &strякщо ви хочете мати рядок лише для читання.


Це неправильно . Тема мінливості вже розглянута у відповіді, що голосує; будь ласка, прочитайте його, щоб дізнатися більше.
Shepmaster
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.