Rust: str vs String

оригинал: Ameya • перевод: Norman Ritchie • обучение • поддержите на Patreon

Будучи новичком в Rust, я запутывался в различных способах представления строк. В книге о языке Rust есть глава «References and Borrowing», в которой используется три различных типа строковых переменных в примерах: String, &String и &str.

Начнём с разницы между str и String: String — это расширяемая, выделяемая на куче структура данных, тогда как str — это неизменяемая строка фиксированной длины, где-то в памяти.

String

Если вы программируете на Java, String в Rust семантически эквивалентен StringBuffer (вероятно это являлось причиной моего замешательства, поскольку я привык приравнивать String с неизменяемостью). Таким образом, String имеет длину и вместимость, тогда как str имеет только один метод len(). В качестве примера:

1
2
3
4
5
6
7
8
9
10
11
let mut s = String::from("Привет, Rust!");
println!("{}", s.capacity()); // напечатает 19
s.push_str("Вот и я!");
println!("{}", s.len()); // напечатает 32

let s = "Привет, Rust!";
// ошибка компиляции: для типа `str` не найден
// метод с именем `capacity`.
println!("{}", s.capacity());

println!("{}", s.len()); // напечатает 19

& str

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

& String

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

1
2
3
4
5
6
7
8
fn main() {
    let s = String::from("Привет, Rust!");
    foo(&s);
}

fn foo(s: &str) {
    println!("{}", s);
}

В приведённом выше примере, foo() может принимать любой строковый срез (слайс) или заимствованный String, что очень удобно. Таким образом, вам почти никогда не надо иметь дело со &String. Единственный случай применения, который приходит мне в голову — если вы хотите передать изменяемую ссылку в функцию, которая должна изменить строку:

1
2
3
4
5
6
7
8
9
fn main() {
    let mut s = String::from("Привет, Rust!");
    foo(&mut s);
}

fn foo(s: &mut String) {
    s.push_str("добавим foo..");
    println!("{}", s);
}

Подводя итог

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

  1. Данные str могут находиться в куче, стеке или в исполняемом файле. Этот отличный ответ на stackoverflow объясняет каждый случай.