Каковы различия между `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
.
Для людей на 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.