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!()

Макрос 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()),
    }
}