Strings

Статья по ссылкам на память в Rust

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

Отображение части строки

Передавать владельца не нужно, передаём в &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(),  // не находим = сообщаем
    });  

Вывод строк

  • Макрос println! позволяет вывести строку в поток stdout;
// println!("Hello there!\n"); 
// раскрывается в такой код:
use std::io::{self, Write};
io::stdout().lock().write_all(b"Hello there!\n").unwrap();
  • Макрос format! позволяет сформировать строку и вернуть из функции;
  • Метод 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);
}    
    
fn output_string(t: &String) -> String {  
    format!(", {}",t)   // возврат сформированной строки  
}

Повтор части строки 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. Если нет, можно использовать &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);

Первая буква в строке

Чтобы проверить или изменить 1-ую букву в строке (в том числе иероглиф или иной вариант алфавита), нужно строку переделать в вектор из букв:

    let char_vec: Vec<char> = text.chars().collect();
    if char_vec[0].is_lowercase() { .. }

Гласные / согласные буквы

Проверку нужно написать в виде функции:

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");

Разворот слов

Дана строка с пробелами между словами. Необходимо развернуть слова в строке наоборот, при этом сохранить пробелы.

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

Нахождение закономерностей в структурах со строками

В примере мы передаём вектор из строк. Далее, анализируем его по частям:

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()),
    }
}

Удаление пробелов в строке 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(" ")
}

Популярные строковые методы