Strings
Статья по ссылкам на память в Rust
Статьи в разделе:
Пример строкового литерала:
let s = "Hello, Rust!"; // Обычная строка, строковый литерал
let raw = r#"Сырой текст с "кавычками""#; // Сырая строка без экранирования
String
Тип данных с владельцем. Имеет изменяемый размер, неизвестный в момент компиляции. Представляет собой векторную структуру:
pub struct String { vec: Vec<u8>; }
Поскольку структура содержит Vec, это значит, что есть указатель на массив памяти, размер строки size структуры и ёмкость capacity (сколько можно добавить к строке перед дополнительным выделением памяти под строку).
&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
&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 её в области жизни нельзя менять содержимое памяти, на которое она ссылается, даже владельцем строки.
Строковые константы
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, переменная всё ещё в области действия!
}
Разделение строки на подстроки
Можно делить с помощью метода split(). В том числе можно делить по нескольким символам разом:
let text = String::from("the_stealth-warrior");
let parts = text2.split(['-', '_']);
for part in parts {
println!("{}", part);
Разница между to_lowercase(), to_ascii_lowercase(), make_ascii_lowercase() и upper-аналогов
| Функция |
to_uppercase() |
to_ascii_uppercase() |
make_ascii_uppercase() |
| Возвращает |
Новая String |
Новая String |
() (меняет входную строку) |
| Текст |
Unicode |
только ASCII |
только ASCII |
| Память |
Новая строка |
Новая строка |
Не выделяет память |
| Speed |
Медленно |
Быстро |
Быстрее всех |
| Особенности |
Обработка спец-символов (ß→SS) |
Нет, пропускает спец-символы |
Нет, пропускает спец-символы |
Популярные строковые методы

Subsections of Strings
Chars
Буквы (char)
Перевод букв в числа методом to_digit(RADIX) (RADIX=10 в десятичной системе) и их сумма:
fn main() {
const RADIX: u32 = 10;
let x = "134";
println!("{}", x.chars().map(|c| c.to_digit(RADIX).unwrap()).sum::<u32>()); }
Перевод букв в коды ASCII и обратно:
println!("{}", 'a' as u8); // перевод символа в код ASCII
println!("{}", 97 as char); // число как символ
// перевод кода UTF-8 в символ (не все символы можно перевести):
println!("{:?}", std::char::from_u32(98).unwrap());
Первая и последняя буква в строке
Чтобы проверить или изменить 1-ую букву в строке (в том числе иероглиф или иной вариант алфавита), нужно строку переделать в вектор из букв:
let char_vec: Vec<char> = text.chars().collect();
if char_vec[0].is_lowercase() { .. }
Для последней буквы есть метод last(), вешается на итератор chars(), возвращает Option<char>, так как последней буквы может и не быть:
println!("{}", "foobar".chars().last().unwrap()); // 'r'
Гласные / согласные буквы
Проверку нужно написать в виде функции:
fn is_vowel(c: char) -> bool {
c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' ||
c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U' }
let text = String::from("Aria");
Сортировка букв в словах
Есть функции sort() и sort_unstable() (работает быстрее, но равные элементы может перемешивать) для вектора символов Vec<char>. Обе функции делают сортировку на месте, возвращают ().
let input = "zxyabc"; // сортировка Unicode тоже работает
let mut char2vec_iter = input.chars()
.collect::<Vec<char>>();
char2vec_iter.sort_unstable(); // сортировка на месте
// char2vec_iter.sort_by(|a,b| b.cmp(a)); // реверс-сортировка
// собираем назад String из Vec<char>
let sorted_string: String = char2vec_iter.into_iter().collect();
println!("{}", sorted_string); // "abcxyz"
chunks() и chunks_exact() разбиение строк на части
Аналогично массивам, строки можно разбить на части:
let text = "ПриветМир";
let chars = text.chars().collect::<Vec<char>>();
println!("Разбиваем строку на части по 3 символа:");
for chunk in chars.chunks(3) {
let s: String = chunk.iter().collect();
println!("{}", s);
// При
// вет
// Мир
Примеры
Разворот букв в словах
Дана строка с пробелами между словами. Необходимо развернуть слова в строке наоборот, при этом сохранить пробелы.
fn reverse_words_split(str: &str) -> String {
str.to_string()
.split(" ") // при разделении split() множественные пробелы сохраняются
.map(|x| x.chars().rev().collect::<String>()) // разворот слов+сбор в строку
.collect::<Vec<String>>(). // сбор всего в вектор
.join(" ") // превращение вектора в строку
}
fn main() {
let word: &str = "The quick brown fox jumps over the lazy dog.";
println!("{}",reverse_words_split(&word));
}
// ehT kciuq nworb xof spmuj revo eht yzal .god
String Iterators
Отображение части строки
Передавать владельца не нужно, передаём в &str:
let s = String::from("Hello World!");
let word = first_word(&s);
println!("The first word is: {}", word);
}
fn first_word(s: &String) -> &str { // передача строки по ссылке
let word_count = s.as_bytes();
for (i, &item) in word_count.iter().enumerate() {
if item == b' ' {
return &s[..i]; // возврат части строки как &str
}
}
&s[..] // обязательно указать возвращаемое значение, если условие в цикле выше ничего не вернёт (например, строка не содержит пробелов = вернуть всю строку)
‘Проход’ по строке итератором
Можно пройти по строке итератором chars() и его методами взятия N-го символа nth() спереди или nth_back() сзади:
let person_name = String::from("Alice");
println!("The last character of string is: {}", match person_name.chars().nth_back(0) { // ищем 1-ый символ с конца строки
Some(i) => i.to_string(), // если находим - превращаем в строку
None => "Nothing found!".to_string(), // не находим = сообщаем
});
matches() и rmatches(),
Возвращают итератор с теми частями строки, которые совпадают с заданным шаблоном:
let v: Vec<&str> = "abcXXXabcYYYabc".matches("abc").collect();
assert_eq!(v, ["abc", "abc", "abc"]); // вывод слева-направо
let v: Vec<&str> = "1abc2abc3".rmatches(char::is_numeric).collect();
assert_eq!(v, ["3", "2", "1"]); // вывод справа-налево
find() и rfind()
Возвращает Option<байт индекс 1го символа в строке слева-направо>, совпадающий с шаблоном. Либо возвращает None, если символ отсутствует в строке. rfind
fn duplicate_encode2(word: &str) -> String {
let s = String::from(word).to_lowercase();
s.chars()
.map(|c| if s.find(c) == s.rfind(c) { '(' } else { ')' })
.collect() } // если у символа есть дубли => замена на '(',
// иначе на ')'
fn main() {
println!("{}", duplicate_encode("rEcede"));
}
Примеры
Повтор части строки n раз
Новый подход использует std::repeat
fn main() {
let repeated = "Repeat".repeat(4);
println!("{}", repeated); // RepeatRepeatRepeatRepeat
}
Старый вариант через итератор - позволяет бесконечно отдавать любое значение (как generic):
use std::iter;
fn main() {
let repeated: String = iter::repeat("Repeat").take(4).collect();
println!("{}", repeated);
}
Удаление пробелов в строке String
Use split(' '), filter out empty entries then re-join by space:
s.trim()
.split(' ')
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(" ")
// Using itertools:
use itertools::Itertools;
s.trim().split(' ').filter(|s| !s.is_empty()).join(" ")
// Using split_whitespace, allocating a vector & string
pub fn trim_whitespace_v1(s: &str) -> String {
let words: Vec<_> = s.split_whitespace().collect();
words.join(" ")
}
Озаглавить каждое слово в предложении
В заданной фразе озаглавить каждое слово. Если результат больше 140 символов или пустой, вернуть None:
fn capitalize_first_letter(s: &str) -> Option<String> {
let res = s
.split_whitespace()
.map(capital) // каждое слово передать в функцию capital()
.collect::<Vec<String>>() // собрать в вектор
.join(" "); // потому что вектор можно собрать в string с join()
if res.len() < 141 || !res.is_empty() { // проверка длины
Some(res)
} else { None } }
fn capital(word: &str) -> String {
let mut lword = word.to_ascii_lowercase();
// изменить НА МЕСТЕ - прямо в этой строке (быстрее всего):
lword[0..1].make_ascii_uppercase();
lword // вернуть итоговую строку
}
String Output
Вывод строк
- Макрос
println! позволяет вывести строку в поток stdout;
// println!("Hello there!\n");
// раскрывается в такой код:
use std::io::{self, Write};
io::stdout().lock().write_all(b"Hello there!\n").unwrap();
- Метод
len() выдаёт длину строки;
- Метод
is_empty() проверят, что строка непустая;
- Метод
contains() ищет одну строку в другой строке;
- Метод
replace(from,to) заменяет часть строки на другую и выдаёт результат;
- Метод
splt_whitespace() позволяет делить строку на части по пробелам;
- Метод
push_str() позволяет добавить текст к строке (строка должна быть mut).
fn main() {
let mut a = String::from("Wonderful RUST World");
println!("Hello{}!", output_string(&a)); // вывод строки
println!("String is empty? {}", a.is_empty());
println!("String length: {}", a.len());
println!("Does string contain 'Hello'? {}", a.contains("Hello"));
println!("{}",a.replace("RUST","Python")); // Wonderful Python World
for i in a.split_whitespace() {
println!("{}", i);
}
a.push_str(" And let's go!");
println!("{}",a);
}
Макрос format! позволяет сформировать строку и вернуть из функции;
fn output_string(t: &String) -> String {
format!(", {}",t) // возврат сформированной строки
}
При этом format!() позволяет конвертировать формат чисел.
Decimal -> HEX:
fn rgb(r: i32, g: i32, b: i32) -> String {
format!(
"{:02X}{:02X}{:02X}", // конвертация dec -> 2 символа UPPER hex
// {:02x} => конвертация в lower hex.
r.clamp(0, 255), // clamp задаёт валидный диапазон чисел
g.clamp(0, 255), // аналог == g.min(255).max(0)
b.clamp(0, 255))}
fn main() {
println!("{}", rgb(1, 2, 3)); // 010203
println!("{}", rgb(255, 255, 255)); // FFFFFF
println!("{}", rgb(-20, 275, 125)); // 00FF7D
}
Decimal -> Binary:
let b = format!("{:b}", 42);
println!("{}", b); // 101010
Пример задачи
Нахождение закономерностей в структурах со строками
В примере мы передаём вектор из строк. Далее, анализируем его по частям:
fn likes(names: &[&str]) -> String {
match names {
[] => "no one likes this".to_string(),
[a] => format!("{} likes this", a),
[a, b] => format!("{} and {} like this", a, b),
[a, b, c] => format!("{}, {} and {} like this", a, b, c),
[a, b, other @ ..] => format!("{}, {} and {} others like this", a, b, other.len()),
}
}