Strings

Links:

Статьи в разделе:

В Rust строки хранятся в формате UTF-8, где каждый символ может занимать от 1 до 4 байт. Поэтому индексация идёт не по символам напрямую, а по байтам или с учётом корректных границ символов (Unicode scalar values).

Пример строкового литерала:

let s = "Hello, Rust!"; // Обычная строка, строковый литерал
let raw = r#"Сырой текст с "кавычками""#; // Сырая строка без экранирования

String

Тип данных с владельцем. Имеет изменяемый размер, неизвестный в момент компиляции. Представляет собой векторную структуру:

pub struct String { vec: Vec<u8>; } // для ASCII символов

Поскольку структура содержит Vec, это значит, что есть указатель на массив памяти, размер строки size структуры и ёмкость capacity (сколько можно добавить к строке перед дополнительным выделением памяти под строку).

Работа с String: если у вас String, нужно сначала получить срез &str с помощью &

&str

Ссылка на часть, slice от String (куча), str (стек) или статической константы. Не имеет владельца, размер фиксирован, известен в момент компиляции.

  • &String можно неявно превращать в &str;
  • &str нельзя неявно превращать в &String.
fn main() {
    let s = "hello_world";
    let mut mut_string = String::from("hello");
    success(&mutable_string);
    fail(s); }

fn success(data: &str) { // неявный перевод &String -> &str
    println!("{}", data); }

fn fail(data: &String) { // ОШИБКА - expected &String, but found &str
    println!("{}", data); }
Warning

Пока существует &str её в области жизни нельзя менять содержимое памяти, на которое она ссылается, даже владельцем строки.

&String

Ссылка на String. Не имеет владельца, размер фиксирован, известен в момент компиляции.

fn change(mystring: &mut String) {
    if !mystring.ends_with("s") {
        mystring.push_str("s");   // добавляем "s" в конец исходной строки
    }

str

Набор символов (литералов), размещённых на стеке. Не имеет владельца, размер фиксирован, известен в момент компиляции. Можно превращать str в String через признак from:

let text = String::from("TEST"); // "TEST" :str

Строковые константы

const CONST_STRING: &'static str = "a constant string"; 

Примеры

Изменение строк

При наличии String, нужно передать ссылку &mut String для изменения:

fn main() {
 let mut mutable_string = String::from("hello ");
 do_mutation(&mut mutable_string);
 println!("{}", mutables_string); // hello world!
}

fn do_mutation(input: &mut String) {
 input.push_str("world!");
}

Строки с владением

Получение String с передачей владения нужно при получении строки от функции, передача в поток (thread):

fn main() {
    let s = "hello_world";
    println!("{}", do_upper(s)); } // HELLO_WORLD

fn do_upper(input: &str) -> String { // возврат String
    input.to_ascii_uppercase() }

Структуры

Если структуре надо владеть своими данными - использовать String. Если нет, можно использовать &str, но нужно указать время жизни (lifetime), чтобы структура не пережила взятую ей строку:

struct Owned { bla: String, }
struct Borrowed<'a> { bla: &'a str, }

fn main() {
    let o = Owned {
        bla: String::from("bla"), };
    let b = create_something(&o.bla); }

fn create_something(other_bla: &str) -> Borrowed {
    let b = Borrowed { bla: other_bla };
    b // при возврате Borrowed, переменная всё ещё в области действия!
}

Разница между to_lowercase(), to_ascii_lowercase(), make_ascii_lowercase() и upper-аналогов

Функцияto_uppercase()to_ascii_uppercase()make_ascii_uppercase()
ВозвращаетНовая StringНовая String() (меняет входную строку)
ТекстUnicodeтолько ASCIIтолько ASCII
ПамятьНовая строкаНовая строкаНе выделяет память
SpeedМедленноБыстроБыстрее всех
ОсобенностиОбработка спец-символов (ß→SS)Нет, пропускает спец-символыНет, пропускает спец-символы

Передача любых строк в функцию с переводом по ссылке

Можно воспользоваться встроенным типажом AsRef для перевода ссылок строки всех видов (String&strBox<str> и т.д.) в &str:

// AsRef<str> is a trait that allows cheap reference-to-reference conversion — here it guarantees we can get a `&str` from a value of type `N`:
fn hello<N: AsRef<str>>(name: N) {
    println!("Hello, {}!", name.as_ref());
    // .as_ref() is from the `AsRef` trait — it converts `&name` into a `&str`.
}

fn main() {
    let strings: Vec<String> = vec!["Alice".into(), "Bob".into()];
    // "Alice".into() равен String::from("Alice")
    
    strings.into_iter().for_each(hello);
    hello("Charlie");
    hello(&String::from("Debbie"));
    hello(String::from("Evan").as_str());
}

Arc str против String

Link: https://www.youtube.com/watch?v=A4cKi7PTJSs&t=822s

Arc<[T]> или он же Arc<str> является предпочтительным вариантом перед Vec<T> (он же String), в случае неизменяемых данных:

  • Дешёвое клонирование, сложность O(1)
  • Меньше размер (16 байт против 24 байт)
  • Включает в себя Deref<[T]>, что позволяет использовать все те же возможности: len(), итерировать, индексировать

Другие особенности:

  • В случае синхронного кода без потоков ещё быстрее-удобнее Rc<str>;
  • Arc<String> не то же самое, что Arc<str> - более сложно и требует двойной указатель, чтобы добраться до данных.