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

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

16 ответов

Решение

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

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

  • в статическом хранилище: строковый литерал "foo" это &'static str, Данные жестко закодированы в исполняемый файл и загружены в память при запуске программы.
  • внутри куча выделяется String: String разыменование к &str вид на String данные.
  • в стеке: например, следующее создает выделенный стеком байтовый массив, а затем получает представление этих данных в виде &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 является кодировкой с переменной шириной, это эффективно заставляет все str быть неизменным. Как правило, мутация требует записи большего или меньшего числа байтов, чем было раньше (например, замена a (1 байт) с ä (2+ байта) потребует больше места в str).

2 На данный момент это может выглядеть только как &str, но динамические типы могут разрешать такие вещи, как Rc<str> для последовательности ссылок считается UTF-8 байтов. Это также не может, str не совсем вписывается в схему DST, поскольку нет версии с фиксированным размером (пока).

Я имею опыт работы с C++, и мне было очень полезно подумать String а также &str в терминах C++:

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

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

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

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

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

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

let a: &str = "hello rust";

Это str это аналогично String, а не кусочек к нему, которые также известны как &str.

An str представляет собой строковый литерал, в основном заранее выделенный текст:

"Hello World"

Этот текст должен где-то храниться, поэтому он хранится в текстовой части исполняемого файла вместе с машинным кодом программы как последовательность байтов ([u8]). Поскольку текст может иметь любую длину, они имеют динамический размер, их размер известен только во время выполнения:

+----+-----+-----+-----+-----+----+----+-----+-----+-----+-----+
|  H |  e  |  l  |  l  |  o  |    |  W |  o  |  r  |  l  |  d  |
+----+-----+-----+-----+-----+----+----+-----+-----+-----+-----+

+----+-----+-----+-----+-----+----+----+-----+-----+-----+-----+
| 72 | 101 | 108 | 108 | 111 | 32 | 87 | 111 | 114 | 108 | 100 |
+----+-----+-----+-----+-----+----+----+-----+-----+-----+-----+

Нам нужно получить доступ к сохраненному тексту, вот где появляется фрагмент.

Срез,[T], представляет собой просмотр блока памяти. Независимо от того, изменчивый или нет, срез всегда заимствует, и поэтому он всегда находится за указателем, &.

Итак, выражение "Hello World" возвращает жирный указатель, содержащий как адрес фактических данных, так и их длину. Этот указатель будет нашим дескриптором фактических данных. Теперь данные находятся за указателем, компилятор знает их размер во время компиляции.

Поскольку текст хранится в исходном коде, он будет действителен в течение всего времени жизни запущенной программы, следовательно, будет иметь static продолжительность жизни.

Итак, возвращаемое значение выражения "Hello Word" должно отражать эти две характеристики, что оно и делает:

let s: &'static str = "Hello World";

Вы можете спросить, почему его тип написан как str но не как [u8], это потому, что всегда гарантируется, что данные будут действительной последовательностью UTF-8. Не все символы UTF-8 являются однобайтовыми, некоторые - 4 байтами, и не все последовательности байтов являются допустимыми символами UTF-8. Так что [u8] будет неточным.

С другой стороны, Stringпредставляет собой специализированный вектор размером u8 байтов, другими словами, буфер изменяемого размера, содержащий текст UTF-8. Мы говорим "специализированный", потому что он не разрешает произвольный доступ и требует определенных проверок того, что данные всегда действительны в кодировке UTF-8. Буфер выделяется в куче, поэтому он может изменять размер своего буфера по мере необходимости или по запросу.

Вот как это определено в исходном коде:

pub struct String {
    vec: Vec<u8>,
}

Вы сможете создавать строки, используя String структура, но vec является закрытым, чтобы гарантировать достоверность и надлежащие проверки, поскольку не весь поток байтов является допустимым символом utf-8.

Но есть несколько методов, определенных для типа String для создания экземпляра String, new - один из них:

pub const fn new() -> String {
  String { vec: Vec::new() }
}

Мы можем использовать его для создания действительной строки. К сожалению, он не принимает входной параметр. Таким образом, результат будет действительным, но пустая строка:

let s = String::new();
println("{}", s);

Но мы можем заполнить этот буфер начальным значением из разных источников:

Из строкового литерала

let a = "Hello World";
let s = String::from(a);

Из сырых деталей

let ptr = s.as_mut_ptr();
let len = s.len();
let capacity = s.capacity();

let s = String::from_raw_parts(ptr, len, capacity);

От персонажа

let ch = 'c';
let s = ch.to_string();

Из вектора байтов

let hello_world = vec![72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100];
// We know it is valid sequence, so we can use unwrap
let hello_world = String::from_utf8(hello_world).unwrap();
println!("{}", hello_world); // Hello World

Из входного буфера

use std::io::{self, Read};

fn main() -> io::Result<()> {
    let mut buffer = String::new();
    let stdin = io::stdin();
    let mut handle = stdin.lock();

    handle.read_to_string(&mut buffer)?;
    Ok(())
}

Или из любого другого типа, который реализует ToString черта

поскольку String является вектором под капотом, он будет демонстрировать некоторые векторные характеристики:

  • указатель: указатель указывает на внутренний буфер, в котором хранятся данные.
  • длина: длина - это количество байтов, хранящихся в данный момент в буфере.
  • емкость: емкость - это размер буфера в байтах. Таким образом, длина всегда будет меньше или равна вместимости.

И он делегирует векторам некоторые свойства и методы:

pub fn capacity(&self) -> usize {
  self.vec.capacity()
}

В большинстве примеров используется String::from, поэтому люди не понимают, зачем создавать String из другой строки.

Это долгое чтение, надеюсь, это поможет.

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

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

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

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

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

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

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

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

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

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

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

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

Rust and


:

  • Rust owned String type, the string itself lives on the heap and therefore is mutable and can alter its size and contents.
  • Because String is owned when the variables which owns the string goes out of scope the memory on the heap will be freed.
  • Variables of type String are fat pointers (pointer + associated metadata)
  • The fat pointer is 3 * 8 bytes (wordsize) long consists of the following 3 elements:
    • Pointer to actual data on the heap, it points to the first character
    • Length of the string (# of characters)
    • Capacity of the string on the heap

:

  • Rust non owned String type and is immutable by default. The string itself lives somewhere else in memory usually on the heap or 'static memory.
  • Because String is non owned when variables goes out of scope the memory of the string will not be freed.
  • Variables of type &str are fat pointers (pointer + associated metadata)
  • The fat pointer is 2 * 8 bytes (wordsize) long consists of the following 2 elements:
    • Pointer to actual data on the heap, it points to the first character
    • Length of the string (# of characters)

Example:

      use std::mem;

fn main() {
    // on 64 bit architecture:
    println!("{}", mem::size_of::<&str>()); // 16
    println!("{}", mem::size_of::<String>()); // 24

    let string1: &'static str = "abc";
    // string will point to `static memory which lives through the whole program

    let ptr = string1.as_ptr();
    let len = string1.len();

    println!("{}, {}", unsafe { *ptr as char }, len); // a, 3
    // len is 3 characters long so 3
    // pointer to the first character points to letter a

    {
        let mut string2: String = "def".to_string();

        let ptr = string2.as_ptr();
        let len = string2.len();
        let capacity = string2.capacity();
        println!("{}, {}, {}", unsafe { *ptr as char }, len, capacity); // d, 3, 3
        // pointer to the first character points to letter d
        // len is 3 characters long so 3
        // string has now 3 bytes of space on the heap

        string2.push_str("ghijk"); // we can mutate String type, capacity and length will aslo change
        println!("{}, {}", string2, string2.capacity()); // defghijk, 8

    } // memory of string2 on the heap will be freed here because owner goes out of scope

}

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

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

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

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

В этих 3 разных типах

       let noodles = "noodles".to_string();
let oodles = &noodles[1..];
let poodles = "ಠ_ಠ"; // this is string literal
  • Строка имеет изменяемый размер буфера, содержащего текст UTF-8. Буфер выделяется в куче, поэтому он может изменять размер своего буфера по мере необходимости или по запросу. В примере «noodles» — это строка, которая владеет восьмибайтовым буфером, семь из которых используются. Вы можете думать о String как о Vec, который гарантированно содержит правильно сформированную UTF-8; на самом деле вот такStringреализуется.

  • A — это ссылка на набор текста UTF-8, принадлежащий кому-то другому: он «заимствует» текст. В этом примере oodles — это &str, относящаяся к последним шести байтам текста, принадлежащего «noodles», поэтому он представляет текст «oodles». Как и другие ссылки на слайсы, a являетсяfat pointer, содержащий как адрес фактических данных, так и его длину. Вы можете думать о a как о не более чем &[u8], который гарантированно содержит правильно сформированный UTF-8.

  • Аstring literalэто&strэто относится к предварительно выделенному тексту, обычно хранящемуся в постоянной памяти вместе с машинным кодом программы. В предыдущем примере poodles — это строковый литерал, указывающий на семь байтов, которые создаются, когда программа начинает выполняться, и сохраняются до ее выхода.

Вот как они хранятся в памяти

Ссылка:Программирование на Rust, Джим Блэнди, Джейсон Орендорф, Леонора Ф. С. Тиндалл

Некоторые использования

example_1.rs

fn main(){
  let hello = String::("hello");
  let any_char = hello[0];//error
}

example_2.rs

fn main(){
  let hello = String::("hello");
  for c in hello.chars() {
    println!("{}",c);
  }
}

example_3.rs

fn main(){
  let hello = String::("String are cool");
  let any_char = &hello[5..6]; // = let any_char: &str = &hello[5..6];
  println!("{:?}",any_char);
}

Shadowing

fn main() {
  let s: &str = "hello"; // &str
  let s: String = s.to_uppercase(); // String
  println!("{}", s) // HELLO
}

function

fn say_hello(to_whom: &str) { //type coercion
     println!("Hey {}!", to_whom) 
 }


fn main(){
  let string_slice: &'static str = "you";
  let string: String = string_slice.into(); // &str => String
  say_hello(string_slice);
  say_hello(&string);// &String
 }

Concat

 // String is at heap, and can be increase or decrease in its size
// The size of &str is fixed.
fn main(){
  let a = "Foo";
  let b = "Bar";
  let c = a + b; //error
  // let c = a.to_string + b;
}

Обратите внимание, что String и &str - это разные типы, и в 99% случаев вам следует заботиться только о &str.

Stringявляется Объектом.

&strуказатель на часть объекта.

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

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

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

В Rust str — это примитивный тип, представляющий последовательность скалярных значений Unicode, также известную как срез строки. Это означает, что это представление строки доступно только для чтения и оно не владеет памятью, на которую указывает. С другой стороны, String — это расширяемый, изменяемый, принадлежащий строковый тип. Это означает, что когда вы создаете строку, она выделяет память в куче для хранения содержимого строки и освобождает эту память, когда строка выходит за пределы области видимости. Поскольку String является расширяемым и изменяемым, вы можете изменить содержимое String после того, как вы его создали.

Как правило, str используется, когда вы хотите сослаться на фрагмент строки, который хранится в другой структуре данных, например String. Строка используется, когда вы хотите создать строковое значение и владеть им.

Нить:

  • Строка — это динамическая, распределенная в куче, расширяемая последовательность символов.
  • Используется для хранения и управления текстовыми данными, размер которых может меняться.
  • Представляет принадлежащие строковые данные.

Пример:

      let s: String = String::from("Hello");
println!("{}", s); 
// Output: Hello

   

&str (Срез строки):

  • &str — это неизменяемая ссылка на последовательность символов (срез), хранящуюся в памяти.
  • Используется для работы со строковыми данными без перехода на владение ими.
  • Может использоваться со строковыми литералами или заимствоваться из String.

Пример:

      fn print_length(s: &str) {
    println!("Length: {}", s.len());
}

let greeting = "Hello";
print_length(greeting); 
// Output: Length: 5

Имейте в виду, что String и &str связаны между собой, но служат разным целям и имеют разные характеристики владения в Rust.

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

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

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

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

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

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

Строка является вектором char, вы можете получить к нему доступ и изменить str.

Другие вопросы по тегам