String printing, splitting, joining and formatting.
В Rust строки хранятся в формате UTF-8, где каждый символ может занимать от 1 до 4 байт. Поэтому индексация идёт не по символам напрямую, а по байтам или с учётом корректных границ символов (Unicode scalar values).
Пример строкового литерала:
lets="Hello, Rust!";// Обычная строка, строковый литерал
letraw=r#"Сырой текст с "кавычками""#;// Сырая строка без экранирования
String
Тип данных с владельцем. Имеет изменяемый размер, неизвестный в момент компиляции. Представляет собой векторную структуру:
pubstructString{vec: Vec<u8>;}// для ASCII символов
Поскольку структура содержит Vec, это значит, что есть указатель на массив памяти, размер строки size структуры и ёмкость capacity (сколько можно добавить к строке перед дополнительным выделением памяти под строку).
Работа с String: если у вас String, нужно сначала получить срез &str с помощью &
&str
Ссылка на часть, slice от String (куча), str (стек) или статической константы. Не имеет владельца, размер фиксирован, известен в момент компиляции.
&String можно неявно превращать в &str;
&str нельзя неявно превращать в &String.
fnmain(){lets="hello_world";letmutmut_string=String::from("hello");success(&mutable_string);fail(s);}fnsuccess(data: &str){// неявный перевод &String -> &str
println!("{}",data);}fnfail(data: &String){// ОШИБКА - expected &String, but found &str
println!("{}",data);}
Warning
Пока существует &str её в области жизни нельзя менять содержимое памяти, на которое она ссылается, даже владельцем строки.
&String
Ссылка на String. Не имеет владельца, размер фиксирован, известен в момент компиляции.
fnchange(mystring: &mutString){if!mystring.ends_with("s"){mystring.push_str("s");// добавляем "s" в конец исходной строки
}
str
Набор символов (литералов), размещённых на стеке. Не имеет владельца, размер фиксирован, известен в момент компиляции. Можно превращать str в String через признак from:
Если структуре надо владеть своими данными - использовать String. Если нет, можно использовать &str, но нужно указать время жизни (lifetime), чтобы структура не пережила взятую ей строку:
structOwned{bla: String,}structBorrowed<'a>{bla: &'astr,}fnmain(){leto=Owned{bla: String::from("bla"),};letb=create_something(&o.bla);}fncreate_something(other_bla: &str)-> Borrowed{letb=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, &str, Box<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`:
fnhello<N: AsRef<str>>(name: N){println!("Hello, {}!",name.as_ref());// .as_ref() is from the `AsRef` trait — it converts `&name` into a `&str`.
}fnmain(){letstrings: 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());}
println!("{}",'a'asu8);// перевод символа в код ASCII
println!("{}",97aschar);// число как символ
// перевод кода UTF-8 в символ (не все символы можно перевести):
println!("{:?}",std::char::from_u32(98).unwrap());
Первая и последняя буква в строке
Чтобы проверить или изменить 1-ую букву в строке (в том числе иероглиф или иной вариант алфавита), нужно строку переделать в вектор из букв:
Есть функции sort() и sort_unstable() (работает быстрее, но равные элементы может перемешивать) для вектора символов Vec<char>. Обе функции делают сортировку на месте, возвращают ().
letinput="zxyabc";// сортировка Unicode тоже работает
letmutchar2vec_iter=input.chars().collect::<Vec<char>>();char2vec_iter.sort_unstable();// сортировка на месте
// char2vec_iter.sort_by(|a,b| b.cmp(a)); // реверс-сортировка
// собираем назад String из Vec<char>
letsorted_string: String=char2vec_iter.into_iter().collect();println!("{}",sorted_string);// "abcxyz"
chunks() и chunks_exact() разбиение строк на части
Аналогично массивам, строки можно разбить на части:
lettext="ПриветМир";letchars=text.chars().collect::<Vec<char>>();println!("Разбиваем строку на части по 3 символа:");forchunkinchars.chunks(3){lets: String=chunk.iter().collect();println!("{}",s);// При
// вет
// Мир
Строковые срезы
Срезы позволяют взять часть строки, указав диапазон байтовых индексов.
Синтаксис: &string[start..end] (где start — начало, end — конец, не включительно).
Особенность: нужно вручную следить за границами байтов, иначе будет паника при попытке разрезать строку посреди многобайтового символа.
lettext="Hello, world!";// тип &str
lettext=String::from("Hello, world!");// String
// Если у вас String, а не &str, нужно взять срез с помощью &:
letfirst_three=&text[0..3];// первые 3 символа
letlast_five=&text[text.len()-5..];// последние 5 символов
println!("{}",first_three);// "Hel"
println!("{}",last_five);// "orld!"
Отрицательные индексы в Rust не поддерживаются, поэтому нужно вычислять вручную. Если указать только начало ([start..]), берётся всё до конца:
Если строка содержит многобайтовые символы (например, кириллицу или эмодзи), простые байтовые срезы могут вызвать панику. Для работы с символами (Unicode scalar values) используйте метод .chars():
lettext="Привет, мир!";letchars: Vec=text.chars().collect();// преобразуем в вектор символов
letprivet: String=chars[0..6].iter().collect();// собираем первые 6 символов
println!("{}",privet);// "Привет"
Метод .chars() возвращает итератор по символам, а .collect() собирает их в нужный тип (например, String).
.get(start..end) для безопасного извлечения
Если нет уверенности в границах, и надо избежать паники, подойдёт метод .get() вместо прямого среза. Он возвращает Option<&str>:
Преимущество: проще для новичков, не нужно беспокоиться о байтовых границах.
Недостаток: добавляет внешнюю зависимость.
Примеры
Разворот букв в словах
Дана строка с пробелами между словами. Необходимо развернуть слова в строке наоборот, при этом сохранить пробелы.
fnreverse_words_split(str: &str)-> String{str.to_string().split(" ")// при разделении split() множественные пробелы сохраняются
.map(|x|x.chars().rev().collect::<String>())// разворот слов+сбор в строку
.collect::<Vec<String>>().// сбор всего в вектор
.join(" ")// превращение вектора в строку
}fnmain(){letword: &str="The quick brown fox jumps over the lazy dog.";println!("{}",reverse_words_split(&word));}// ehT kciuq nworb xof spmuj revo eht yzal .god
lets=String::from("Hello World!");letword=first_word(&s);println!("The first word is: {}",word);}fnfirst_word(s: &String)-> &str{// передача строки по ссылке
letword_count=s.as_bytes();for(i,&item)inword_count.iter().enumerate(){ifitem==b' '{return&s[..i];// возврат части строки как &str
}}&s[..]// обязательно указать возвращаемое значение, если условие в цикле выше ничего не вернёт (например, строка не содержит пробелов = вернуть всю строку)
‘Проход’ по строке итератором
Можно пройти по строке итератором chars() и его методами взятия N-го символа nth() спереди или nth_back() сзади:
letperson_name=String::from("Alice");println!("The last character of string is: {}",matchperson_name.chars().nth_back(0){// ищем 1-ый символ с конца строки
Some(i)=>i.to_string(),// если находим - превращаем в строку
None=>"Nothing found!".to_string(),// не находим = сообщаем
});
matches() и rmatches(),
Возвращают итератор с теми частями строки, которые совпадают с заданным шаблоном:
Возвращает Option<байт индекс 1го символа в строке слева-направо>, совпадающий с шаблоном. Либо возвращает None, если символ отсутствует в строке. rfind
fnduplicate_encode2(word: &str)-> String{lets=String::from(word).to_lowercase();s.chars().map(|c|ifs.find(c)==s.rfind(c){'('}else{')'}).collect()}// если у символа есть дубли => замена на '(',
// иначе на ')'
fnmain(){println!("{}",duplicate_encode("rEcede"));}
Use split(' '), filter out empty entries then re-join by space:
s.trim().split(' ').filter(|s|!s.is_empty()).collect::<Vec<_>>().join(" ")// Using itertools:
useitertools::Itertools;s.trim().split(' ').filter(|s|!s.is_empty()).join(" ")// Using split_whitespace, allocating a vector & string
pubfntrim_whitespace_v1(s: &str)-> String{letwords: Vec<_>=s.split_whitespace().collect();words.join(" ")}
Озаглавить каждое слово в предложении
В заданной фразе озаглавить каждое слово. Если результат больше 140 символов или пустой, вернуть None:
fncapitalize_first_letter(s: &str)-> Option<String>{letres=s.split_whitespace().map(capital)// каждое слово передать в функцию capital()
.collect::<Vec<String>>()// собрать в вектор
.join(" ");// потому что вектор можно собрать в string с join()
ifres.len()<141||!res.is_empty(){// проверка длины
Some(res)}else{None}}fncapital(word: &str)-> String{letmutlword=word.to_ascii_lowercase();// изменить НА МЕСТЕ - прямо в этой строке (быстрее всего):
lword[0..1].make_ascii_uppercase();lword// вернуть итоговую строку
}
Макрос println! позволяет вывести строку в поток stdout;
// println!("Hello there!\n");
// раскрывается в такой код:
usestd::io::{self,Write};io::stdout().lock().write_all(b"Hello there!\n").unwrap();
Макрос dbg!() позволяет вывести переменные и структуры в поток stdout;
split()
Метод split: разбивает строку на части по указанному разделителю и возвращает итератор. Разделитель может быть символом, строкой или даже пробелом. В том числе можно делить по нескольким символам разом:
lettext=String::from("the_stealth-warrior");letparts=text.split(['-','_']).collect::<Vec<&str>>();// collect собирает в коллекцию типа вектор
forpartinparts{println!("{}",part);
Другие методы разбивки
split_whitespace()
Разбивает по любым пробельным символам (пробелы, табы, переносы строк).
Нахождение закономерностей в структурах со строками
В примере мы передаём вектор из строк. Далее, анализируем его по частям:
fnlikes(names: &[&str])-> String{matchnames{[]=>"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()),}}