Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Тема
Описание
Доп.

Другие типы, похожие на строки

При работе с Юникод-текстом применяйте только типы String и &str, но:

  • при работе с именами файлов пользуйтесь типами std::path::PathBuf и &Path
  • при работе с двоичными данными пользуйтесь типами Vec<u8> и &[u8]
  • при работе с именами переменных среды и аргументами командной строки в формате операционной системы пользуйтесь типами OsStr и OsString
  • при взаимодействии с библиотеками, написанными на C, в которых строки заканчиваются нулем, пользуйтесь типами std::ffi::CString и &CStr

блестящая концепция UTF-8

software unicode and character

ASCII (от английского American Standard Code for Information Interchange, Американский стандартный код для обмена информацией) — это стандарт кодировки символов, используемый для представления текстовых данных в компьютерах, сетях и других устройствах, работающих с текстом.

ASCII использует 7 бит для кодирования каждого символа, что даёт возможность закодировать 128 символов. ASCII не поддерживает символы, отличные от английского алфавита. Например, буквы с диакритическими знаками (é, ü) или символы других языков (кириллица, арабский, китайский) не входят в стандарт ASCII. Для решения этих ограничений были разработаны расширения, такие как ISO 8859-1 (для западноевропейских языков) и Unicode (глобальный стандарт). Не имеет смысла иметь строку, не зная, какую кодировку она использует. Вы больше не можете засунуть голову в песок и притворяться, что «простой» текст — это ASCII.

UTF-8 была еще одной системой для хранения строки кодовых точек Unicode, этих магических чисел U+, в памяти с использованием 8-битных байтов. В UTF-8 каждая кодовая точка (т.е. символ) от 0 до 127 хранится в одном байте. Только кодовые точки 128 и выше хранятся с использованием 2, 3, фактически, до 6 байтов.

Это имеет приятный побочный эффект, что английский текст выглядит точно так же в UTF-8, как и в ASCII, поэтому американцы даже не замечают ничего неправильного. Только остальной мир должен прыгать через обручи. В частности, Hello , который был U+0048 U+0065 U+006C U+006C U+006F, будет сохранен как 48 65 6C 6C 6F, что, узрите! то же самое, что и в ASCII, и ANSI, и в каждом наборе символов OEM на планете. Теперь, если вы настолько смелы, чтобы использовать буквы с ударением, греческие буквы или буквы клингонского алфавита, вам придется использовать несколько байтов для хранения одной кодовой точки, но американцы никогда этого не заметят.

String - это вектор байтов u8 Vec<u8> скалярных значений юникод

&str - это срез вектора байтов u8 (&[u8])

std::fmt::Display для пользователя

std::fmt::Debug для программиста


fn main(){
    let text = "hello\nworld ";
    println!("{}", text);// Display
    println!("{:?}", text); // Debug "hello\nworld "
}

crate smartstring

Предполагаемое использование для SmartString - это тип ключа для B-дерева (например, std::collections::BTreeMap) или любой операции с массивом, где локальность кэша имеет решающее значение.

В целом, это хороший тип данных для уменьшения выделения памяти в куче и повышения локальности строковых данных. Если вы используете SmartString в качестве замены для String, вы почти наверняка увидите небольшой прирост производительности, а также небольшое снижение использования памяти.

crates compact_str, smartstring, smol_str, strumbra

an-optimization-thats-impossible-in-rust

Решают схожую задачу — оптимизацию хранения строк, снижая количество аллокаций и уменьшая использование памяти, особенно для коротких строк. Позволяет хранить короткие строки прямо на стеке, а не выделять отдельную память в куче

Отличительной особенностью smartstring является то, что она может работать как в режиме ASCII, так и с более сложными наборами символов, и её можно использовать как замену стандартного String с небольшими изменениями в коде.

use std::io::Write;

write! Запишите отформатированные данные в буфер

writeln! - Запишите отформатированные данные в буфер с добавлением новой строки

macro.writeln


fn main(){
 // Записать отформатированные данные в буфер
     use std::io::Write;

    let mut w:Vec = Vec::new();
    write!(&mut w, "test").unwrap();
    write!(&mut w, "formatted {}", "arguments").unwrap();
    //assert_eq!(w, b"testformatted arguments");

    let mut w = String::new();
    write!(&mut w, "test").unwrap();
    write!(&mut w, "formatted {}", "arguments").unwrap();
    assert_eq!(w, "testformatted arguments");
 
    let mut w:Vec= Vec::new();
    writeln!(&mut w)?;
    writeln!(&mut w, "test")?;
    writeln!(&mut w, "formatted {}", "arguments")?;

    assert_eq!(&w[..], "\ntest\nformatted arguments\n".as_bytes());
}

include_bytes!() - создает &'static [u8; N] из файла байт

include_str!() - создает &str из файла байт

macro.include_bytes


fn main(){
    let my_str:&str = include_str!("spanish.in");
    assert_eq!(my_str, "adiós\n");
}

Индексация

доступ к символу

перебор

Как правило, доступ к вектору с помощью [] является очень быстрой операцией. Но поскольку каждый символ в строке, закодированной UTF-8, может быть представлен несколькими байтами, то при поиске вы должны перебрать n-ое количество литер в строке.


fn main(){
    let hachiko = "忠犬ハチ公";
    for b in hachiko.as_bytes() {
        print!("{}, ", b); // 229, 191, 160, 231, 138....
    }
    for c in hachiko.chars() {
        print!("{}, ", c); // 忠, 犬, ハ, チ, 公,
    }

    let dog = hachiko.chars().nth(1); // что-то вроде hachiko[1]

    // Это индексы байтов, а не символов (работает только  если не ломать символ неверным попаданием в байты)
    let s = String::from("hello world");
    let hello:&str = &s[0..5];
    let hello:&str = &s[0..=4];
    let hello:&str = &s[..5];

    // Ошибка доступа к символу по позиции байта (разломали символ, но `&dog[0..3];` корректно)
    let dog = "忠犬ハチ公";
    let hachi = &dog[0..2];
}

Индексы диапазона фрагментов строки должны иметь место при действительных границах символов UTF-8. Если вы попытаетесь создать срез строки в середине многобайтового символа, ваша программа выйдет с ошибкой.

Строковые литералы - способы создания текстовых значений

r#"..."#

reference/tokens

cheats.rs/#strings-chars


fn main(){
// Обычные экранированные символы ASCII  
    println!("\n\r\t\0\\");

// ASCII 8-битный код символа (ровно 2 цифры) до 7f
    println!("\x36"); // 6

// 24-битный код символа Unicode (до 6 цифр)
    println!("\u{7fff}"); // 翿

// Bite
    let res:u8 = b'H';
    println!("{res}"); // 72

// Необработанная строка (строками содержащие специальные символы, которые не нужно экранировать)
    println!(r#"C:\Program Files\Rust\bin"#); // C:\Program Files\Rust\bin

// Необработанная строка байтов (строками содержащие специальные символы, которые не нужно экранировать)
let http_request:&[u8; 32]  = br#"GET / HTTP/1.1
Host: example.com"#;
    println!("Размер: {}", http_request.len());// Размер: 32

// Символьный литерал, фиксированный 4-байтовый Unicode char
    println!("{}", '🦀');

// создания C-совместимых строк, которые используются в Foreign Function Interface (FFI)
// C string - создаёт статический массив байтов, который всегда оканчивается нулевым байтом (`\0`). Этот нулевой байт — это так называемый null-terminator, который служит признаком конца строки в языке C. Обычные строки в Rust не имеют такого завершающего символа, поэтому при передаче в C-функции без `\0` может произойти чтение некорректных данных или краш программы.
    use std::ffi::CStr;
    let c_str_literal = c"hello";
    println!("{:?}", c_str_literal);

// Raw C string Эта форма используется, когда в строке есть символы, которые могли бы быть восприняты как escape-последовательности, например, `\n, \t или \`.
    let path_str = cr#"C:\Users\John\file.txt"#; // Обычный c"\t" стал бы символом табуляции
    println!("{:?}", path_str); // "C:\\Users\\John\\file.txt"
}


fn main(){
    let hello = r###" китайские "иероглифы" и 'еще' кавычки что-то "###; 
// ------------------------------------------------------------------------------------------------------
    let shift_jis = b"\x82\xe6\x82\xa8\x82\xb1\x82"; // "ようこそ" in SHIFT-JIS

    match str::from_utf8(shift_jis) {
        Ok(my_str) => println!("Conversion successful: '{}'", my_str),
        Err(e) => println!("Conversion failed: {:?}", e),
    };
// ------------------------------------------------------------------------------------------------------
// 'a': char // 32 бита
// b'a': u8

// Байтовый срез
let method: &[u8; 3] = b"GET";
assert_eq!(method, &[b'G', b'E', b'T']);
// ------------------------------------------------------------------------------------------------------

    let s = "hello  
world"; // многострочный литерал

    let s = "hello \  // \ убирает \n и пробелы после него
world";
// ------------------------------------------------------------------------------------------------------
  
let s = r#"This is
a multiline
string"#;
    
    println!("{}", s);
}

Спец символы для упрощения экранирования r , r#


fn main(){
    let _str = "Escapes don't work here: \x3F \u{211D}";// ? ℝ
    println!("{}", _str);
    // как есть, сырой указатель
    let raw_str = r"Escapes don't work here: \x3F \u{211D}";//  \x3F \u{211D}
    println!("{}", raw_str);
    
    //  необработанная строка
    let quotes = r#"And then I said: "There is no escape!""#;
    println!("{}", quotes);
    
    let longer_delimiter = r###"A string with "# in it. And even "##!"###;
    println!("{}", longer_delimiter);
}

Сравнение String и &str или другого типа PartialEq

Почему cmp работает с &str если тип T это String? По идее должны ожидать &String.

Так бы было если бы мы не определили в bounds отдельный тип V который не завязан на типе T структуры.

Для примера cmp2 как раз подвязан к структуре и ожидает &String

Річ у тому як викликається trait PartialEq.

Якщо ви пишете v == self.0, то буде викликатися v.eq(self.0).

Якщо ви пишете self.0 == v, то буде викликатися self.0.eq(v), а це помилка, тому що для T ніде не вимагається PartialEq<V>.

Якщо ви не вказуєте generic параметер для PartialEq, то там по дефолту Self.

Тобто T: PartialEq означає T: PartialEq<T>


use std::cmp::PartialEq;

pub struct Foo(T);
impl Foo {
    pub fn new(key: T) -> Self {
        Self(key)
    }
}
impl Foo {
    pub fn cmp>(&self, v: V) -> bool {
        v == self.0
        // self.0 == v // для такого сравнения типа надо добавить   ` -> bool where T:PartialEq`
    }
    pub fn cmp2(&self, v: &T) -> bool {
        v == self.0
    }
}

fn main() {
    let f = Foo::new("hello".to_owned());
    
    assert!(f.cmp("hello"));
    assert!(f.cmp("hello".to_string()));
    
    let f = Foo::new(1);
    assert!(f.cmp(1));
}

Когда стоит использовать String, а когда &str в структурах?

Еще Deserialize структуры с lifetime не работает, только если делать Deserialize wraper'a над этой структурой

Нам следует использовать ссылки, если наша структура не требует владения переменной.

Нужно ли использовать переменную вне структуры? - берем &str

Мой тип большой? Если тип большой, то передача по ссылке позволит сберечь память.

Буфер типа String с большим количеством данных может значительно замедлить программу.

По поводу 'static

Мы можем вместо String передать строку с постоянным временем жизни, но она будет занимать место всегда в программе (в бинарном файле) !


struct Person {
    name: &'static str,
}
impl Person {
    fn greet(&self) {
        println!("Привет, меня зовут {}", self.name);
    }
}
fn main() {
    let person = Person { name: "Herman" };
    person.greet();
}


#[derive(Clone)]
struct User{
    username:String // структура должна владеть своими данными которые принадлежа ей
}
struct User2<'a>{
    username: &'a str; // если структура не владеет своими данными они будут уничтожены согласно правилам времени жизни
}
fn main() {
    let username: String = String::from("Jeka");
    let user: User = User{username};

    let username:&str = "Jeka";
    let user2:User2 = User2{username};

    let user2:User2 = User2{username: &user.username};
}

Преобразования строк в Vec<u8>

Преобразования строк в &[u8]

Преобразования строк в &str


fn main(){
    let my_string = String::from("hello");
    let my_bytes: Vec = my_string.into_bytes();

    let my_str: &str = "hello";
    let my_bytes: &[u8] = my_str.as_bytes();

    let my_vec = vec![104, 101, 108, 108, 111]; // "hello" в байтах
    let my_string = String::from_utf8(my_vec)
        .expect("Вектор байтов должен быть валидным UTF-8");

    use std::str;
    let my_slice: &[u8] = &[104, 101, 108, 108, 111];
    let my_str: &str = str::from_utf8(my_slice)
        .expect("Срез байтов должен быть валидным UTF-8");

}

Одиночное скалярное значение Unicode (представлен четерьмя байтами) в одинарных кавычках

Unicode — это в первую очередь про символы. Кодпоинты символов Unicode принадлежат диапазону от U+0000 до U+10FFFF

Кодпоинты Unicode обозначаются как U+xxxx, где xxxx — шестнадцатеричный код символа.

char – это 32-разрядное значение, содержащее кодовую позицию Unicode.

Гарантируется, что char находится в диапазоне от 0 до 0xd7ff или от 0xe000 до 0x10ffff все методы создания и изменения значений типа char соблюдают это условие.


fn main(){
    let x:char = 'x';
}

Size char = 4 bytes

Чтобы быть уверенным, что char может содержать любой символ, он должен быть 4 байта.

Так как 2 байта (u16) наибольшее число, которое вы можете составить, это 65 535, что значительно меньше количества букв во всех языках мира (одних только китайских иероглифов больше!).

Но у u32 (4 байта) предлагает более чем достаточно места, позволяя разместить до 4 294 967 295 букв, поэтому char это u32 Но всегда использовать 4 байта нужно только для char типа.

Строки (String) отличаются и не всегда используют 4 байта на один символ. Когда символ является частью строки String или &str (не типа char), строка кодируется так, чтобы использовать наименьшее количество памяти, необходимое для каждого символа.

Основные методы типа char в Rust включают проверку символа на соответствие определённым категориям, а также преобразование регистра. Тип char в Rust представляет собой один скалярный Unicode-символ (4 байта), а не байт, как в C/C++.

method.to_digit

Методы для проверки символа

  • is_alphabetic(): Проверяет, является ли символ буквой.
  • is_alphanumeric(): Проверяет, является ли символ буквой или цифрой.
  • is_ascii(): Проверяет, находится ли символ в диапазоне ASCII (от 0 до 127).
  • is_control(): Проверяет, является ли символ управляющим символом Unicode (например, табуляция, перевод строки).
  • is_digit(radix: u32): Проверяет, является ли символ цифрой в указанной системе счисления (от 2 до 36).
  • is_lowercase(): Проверяет, является ли символ буквой в нижнем регистре.
  • is_uppercase(): Проверяет, является ли символ буквой в верхнем регистре.
  • is_whitespace(): Проверяет, является ли символ пробелом, табуляцией, переводом строки и другими символами-разделителями.

Методы для преобразования регистра

  • to_lowercase(): Возвращает итератор, который преобразует символ в нижний регистр. Может возвращать несколько символов, если преобразование приводит к расширению (например, Ş -> s и s).
  • to_uppercase(): Возвращает итератор, который преобразует символ в верхний регистр.
  • to_ascii_lowercase(): Преобразует символ в нижний регистр только если он является ASCII-буквой. Возвращает сам символ, если он не ASCII-буква.
  • to_ascii_uppercase(): Преобразует символ в верхний регистр только если он является ASCII-буквой.

Дополнительные методы

  • len_utf8(): Возвращает количество байтов, необходимое для кодирования символа в UTF-8.
  • len_utf16(): Возвращает количество 16-битных кодовых единиц (surrogates), необходимое для кодирования символа в UTF-16.
  • escape_unicode(): Возвращает итератор, который генерирует строковое представление символа в виде Unicode-экранированной последовательности.

fn main() {
    let c1 = 'a';
    let c2 = 'B';
    let c3 = '8';
    let c4 = ' ';

    println!("{} is alphabetic: {}", c1, c1.is_alphabetic());
    println!("{} is lowercase: {}", c2, c2.is_lowercase());
    println!("{} is a digit (base 10): {}", c3, c3.is_digit(10));
    println!("{} is whitespace: {}", c4, c4.is_whitespace());
    
    let upper_case_c1: String = c1.to_uppercase().collect();
    println!("{} to uppercase is: {}", c1, upper_case_c1);
    
    let lower_case_c2: String = c2.to_lowercase().collect();
    println!("{} to lowercase is: {}", c2, lower_case_c2);
    
    let a_byte_len = 'a'.len_utf8();
    let cyrillic_byte_len = 'Я'.len_utf8();
    println!("'a' takes {} byte(s)", a_byte_len);
    println!("'Я' takes {} byte(s)", cyrillic_byte_len);

    let unicode_escape = '⌘'.escape_unicode();
    println!("'⌘' escaped: {}", unicode_escape);

    assert_eq!('β'.is_alphabetic(), true);
    assert_eq!('8'.to_digit(10), Some(8));
    assert_eq!('ಠ'.len_utf8(), 3);
    assert_eq!(std::char::from_digit(2, 10), Some('2'));
}

Графема может состоять из одной или нескольких скалярных точек Юникода.

Символ 'ё' состоит из одной скалярной точки Юникода (char). Это наименьшая неделимая единица текста в Rust. Она соответствует одному значению в кодовой таблице Юникода.


fn main() {
    let c = 'ё'; // Это валидный код
    println!("Символ: {}", c);
}

Графема может состоять из одной или нескольких скалярных точек Юникода.

Символ é может быть представлен двумя способами:

  • Как одна скалярная точка Юникода (\u{00E9}), то есть как один char.
  • Как комбинация двух скалярных точек: e (\u{0065}) + диакритический знак ´ (`u{0301}`). В этом случае это будет две char, но вместе они образуют одну графему.

use unicode_segmentation::UnicodeSegmentation;

fn main() {
    let s = "e\u{0301}"; // Строка, состоящая из 'e' + диакритика '́'
    
    // Если мы итерируем по char, мы получим два символа
    println!("Символы (char):");
    for c in s.chars() {
        println!("{}", c); // Выведет: 'e', затем '́'
    }

    // Если мы используем библиотеку, мы получим одну графемe
    println!("\nГрафемы (graphemes):");
    for g in s.graphemes(true) {
        println!("{}", g); // Выведет: 'é'
    }
}

'৬'.is_numeric()

'1'.is_digit(10)

'1'.to_digit(10) Преобразует символ в число 10 системы


fn main(){
//is_digit  По сравнению с is_numeric () эта функция распознает только символы 0-9, a-z и A-Z.
//is_numeric распознает странные цифры
    
    // Итерируемся по char и приводим к 10-й системе счета
    let mut chars = "123456৬".chars();

    let mut v: Vec = vec![];

    while let  Some( s) = chars.next(){
        
         if s.is_numeric() && s.is_digit(10){
            if let  Some(n) = s.to_digit(10){
               v.push(n)
            }
        }
    }
    println!("{:?}",v);// [1, 2, 3, 4, 5, 6]

    assert!('٣'.is_numeric());
    assert!('7'.is_numeric());
    assert!('৬'.is_numeric());
}

Преобразование char в u8

TryInto Chars


fn main(){
    // Преобразование char в u8
    // Так как char это Unicode равен 4 байта с диапазоном от 0 до 4294967295,  а u8 это 1 байт с диапазоном 0-255
    // То следует проверять диапазон try_into / try_from
    let c:char = 'c';
    let b:u8 = c.try_into().expect("unicode character not in u8 range");
    let b:u8 = u8::try_from('c').expect("unicode character not in u8 range");
}

fn main(){
    let s = String::from("love: ❤️");
    let v: Vec = s.chars().collect();
}

В типе char всегда хранится кодовая позиция Юникода из диапазона 0x0000—0xD7FF или 0xE000—0x10FFFF.

В нем никогда не хранится половина суррогатной пары (т. е. кодовая позиция из диапазона 0xD800—0xDFFF) или значение, выходящее за пределы кодового пространства Юникода (большее 0x10FFFF).

Для контроля допустимости значений типа char в Rust используется как система типов, так и динамические проверки.


fn main(){
    assert_eq!('*' as i32, 42);
    assert_eq!('ಠ' as u16, 0xca0);
    assert_eq!('ಠ' as i8, -0x60); // U+0CA0 усечено до восьми бит, со знаком
}

Обратное преобразование в char допускает только тип u8 Функция std::char::from_u32, которая принимает произвольное значение типа u32 и возвращает значение типа Option<char>


fn main(){
    assert_eq!(Some('*'),std::char::from_u32(42));
}

Если кодовая позиция находится в диапазоне от U+0000 до U+007F (т. е. принадлежит набору символов ASCII), то символ можно записывать в виде '\xHH', где HH – две шестнадцатеричные цифры.

Например, символьные литералы '*' и '\x2A' эквивалентны, поскольку кодовая позиция символа * равна 42, или 2A в шестнадцатеричном виде.


fn main(){
    assert_eq!('*' as i32, 42);
}

Любой символ Юникода можно записать в виде '\u{HHHHHH}' , где HHHHHH – шестнадцатеричное число, содержащее от одной до шести цифр. Например, символьный литерал '\u{CA0}' представляет символ «ಠ» языка каннада, используемый для обозначения неодобрения в Юникоде: «ಠ_ಠ». Тот же самый литерал можно записать просто как ' ಠ ' .

HEX decode


fn main(){
 let code_hex:Vec =  vec![0x52, 0x49, 0x46, 0x46];
 let decode:String = std::char::decode_utf16(code_hex)
    .map(|r| r.map_err(|e| e.unpaired_surrogate()))
    .map(|v| v.unwrap())
    .collect::();
 assert_eq!("RIFF".to_string(),decode) ;
}


fn main(){
 let mut b = [0; 2];
 let result:&mut[u16] = '*'.encode_utf16(&mut b);
 assert_eq!(&[42],result);
 
 let s:char = std::char::from_u32(42).unwrap();
 assert_eq!('*',s);
 
 let code = &[0x2A].to_vec();
 let decode:String = std::char::decode_utf16(code.to_vec()) .map(|r| r.map_err(|e| e.unpaired_surrogate())) .map(|v| v.unwrap()).collect::();
 assert_eq!("*",decode);
}

std::string::String

(методы &str благодаря Deref<Target=str>)

struct.String

strings

В Rust строки являются последовательностями символов Юникода, но в памяти они хранятся не как массивы символов типа char, а в кодировке переменной ширины UTF-8.

Каждый символ ASCII в строке занимает один байт, а все прочие символы – несколько байтов.


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

fn main() {
  let s:String = "hello".to_string();
  // *s - сперва мы преобразуем String в str через Deref
  // &*s - далее на str берем ссылку &str
  foo(&*s); // неявное преобразование Deref
  foo(&*std::ops::Deref::deref(&s)); // явное преобразование Deref
}

String выглядит так в исходниках Rust

strings-in-rust

String выглядит так в исходниках Rust

8 byte - ptr адрес памяти первого байта в куче, на который он указывает, используя свои первые 8 байтов 8 byte - len для хранения длины этой строки 8 byte - емкость capacity

from_raw_parts() - unsafe Создает String из raw данных (указатель,длина,емкость).

into_raw_parts() - Возвращает необработанный указатель на raw данные (указатель,длина,емкость)


dbg!(std::mem::size_of::());// 24 byte (8+8+8) (Data, length, capacity)

На самом деле String выглядит так в исходниках Rust.
pub struct String { 
    vec: Vec, 
}

Мы также можем собрать его вручную и разобрать на необработанные части. 
#![feature(vec_into_raw_parts)]
fn main() {
  unsafe {
      let bytes: &mut [u8] = &mut [0x62, 0x61, 0x6e, 0x61, 0x6e, 0x61];
      let string: String = String::from_raw_parts(bytes.as_mut_ptr(), 6, 6);
       // Data, length, capacity
      let (bytes, length, capacity) = string.into_raw_parts();
  }
}

Строка состоит из трех компонентов: указателя на некоторые байты, длины и емкости.

from_raw_parts() - unsafe Создает String из raw данных (указатель, длина, емкость).

into_raw_parts() - Возвращает необработанный указатель на raw данные (указатель, длина, емкость)

Строка состоит из трех компонентов: указателя на некоторые байты, длины и емкости. Умный указатель так как владеют некоторой памятью и позволяют вам манипулировать ею. Указатель указывает на внутренний буфер, используемый String для хранения своих данных. Длина - это количество байтов, которые в настоящий момент хранятся в буфере, а емкость - это размер буфера в байтах.

  • указатель на некоторые байты - as_ptr
  • длина - len
  • емкость - capacity
use std::mem;
fn main(){
  unsafe {
     let s = String::from("hello");
     let mut s = mem::ManuallyDrop::new(s);// Prevent automatically dropping the String's data
     let ptr = s.as_mut_ptr();
     let len = s.len();
     let capacity = s.capacity();
     let s = String::from_raw_parts(ptr, len, capacity);
     assert_eq!(String::from("hello"), s);
  }
}

fn main(){
   //String
   let mut s:String = "Hello".to_string();
   let mut s = std::mem::ManuallyDrop::new(s);// Так как реализация Drop уже есть, то нам нужно запретить автоматическое удаление данных
   
   let ptr:*mut u8 = s.as_mut_ptr();
   let len = s.len();
   let capacity = s.capacity();
   unsafe{
        for i in 0..len{
           // мутируем строку
           // изменим регистр первого символа с H => h
            *ptr.add(0) = *(String::from_utf8(vec!(*ptr.add(0))).unwrap()).to_ascii_lowercase().as_bytes().first().unwrap();
            println!("{}", *ptr.add(i) );//[72, 101, 108, 108, 111]
        }
    }
   println!("address = {:p}", ptr );
   // Восстановление строки
    let s = unsafe {
        let v:Vec<u8> = Vec::from_raw_parts(ptr, s.len(),s.capacity());
        //println!("{:?}", v );//[72, 101, 108, 108, 111]
        String::from_utf8( v ).unwrap()
    };
    assert_eq!(String::from("hello"), s);
   // или так
   let s = unsafe { String::from_raw_parts(ptr, len, capacity) } ;
   assert_eq!(String::from("hello"), s);
}

Создание String из &str

String::from_raw_parts


fn main(){
    use std::mem;
    let story = "Once upon a time...";

    let ptr:*const u8 = story.as_ptr();
    let len:usize = story.len();
    let capacity:usize = story.len();

    // story has nineteen bytes
    assert_eq!(19, len);

    // Теперь, когда у нас есть компоненты (ptr,len,capacity), мы удаляем story.
    mem::forget(story);

    // Мы можем создать строку из ptr, len и capacity. 
    // Это все небезопасно, потому что мы несем ответственность за действительные компоненты  (ptr,len,capacity)
    let s:String = unsafe { String::from_raw_parts(ptr as *mut _, len, capacity) } ;

    assert_eq!(String::from("Once upon a time..."), s);
}

функция принимает String или &str (пример для обобщеннения)

impl Into<String>

impl AsRef<str>

impl From<&str> for String "auto implies" => Into<String> for &str

creating-a-rust-function-that-accepts-string-or-str


// Если нужна только ссылка (чтение)
// Используйте AsRef — функция будет принимать всё, что можно превратить в &str (String, &String, &str, Arc, и т. д.):
fn take_a_str(some: impl AsRef) { // или fn take_a_str>(some: S){...
    let some = some.as_ref();
    println!("{some}");
}

// Если нужно владение (String внутри функции)
// Используйте Into — функция примет и String, и &str (второе будет скопировано):
use core::fmt::Debug;
fn take_a_str_into(some: impl Into+Debug) {// или fn take_a_str_into+Debug>(some: S) {...
    println!("{:?}",some);
}
fn main() {
    take_a_str("str");
    take_a_str("String".to_string());
    
    // also `&String` is supported:
    let string_ref = "StringRef".to_string();
    take_a_str(&string_ref);

    take_a_str_into("str");
    take_a_str_into("String".to_string());
    let string_ref = "StringRef".to_string();
    take_a_str_into(&string_ref);
}

функция возвращает String или &str

creating-a-rust-function-that-returns-string-or-str


// Удалить пробелы.Если входные данные будут мутироваться то вернем String иначе возвращать входную строку &str
// Т.е. клонирование при мутации Cow, отложите выделение памяти как можно на дольше.
use std::borrow::Cow;
fn remove_spaces<'a>(input: &'a str) -> Cow<'a, str> {
    if input.contains(' ') {
        input
        .chars()
        .filter(|&x| x != ' ')
        .collect::()
        .into()
    } else {
       // input.into() // Into> 
       // или полный синтаксис
        Into::>::into(input)
    }
} 
// Вариант 2: еше и присылать String или &str (вариант с AsRef)
fn remove_spaces<'a>(input: &'a(impl AsRef + ?Sized)) -> Cow<'a, str> {
    let input = input.as_ref();
    if input.contains(' ') {
        input
        .chars()
        .filter(|&x| x != ' ')
        .collect::()
        .into()
    } else {
        // input.into() // Into> 
        // или полный синтаксис
        Into::>::into(input)  
    }
} 
fn main(){
    let s = remove_spaces("Herman"); // Cow::Borrowed  
    let len = s.len(); // impl Deref
    let owned: String = s.into_owned(); // memory is allocated for a new string

    let binding = "Herman Radtke".to_string();
    let s = remove_spaces(&binding); // Cow::Owned 
    let len = s.len(); // impl Deref
    let owned: String = s.into_owned(); // no new memory allocated as we already had a String
}

Получить 'static str из String

unsafe


fn get_static(s: String) -> (&'static str,*mut String){
    let my_speed: Box = Box::new(s);
    let my_speed_ptr: *mut String = Box::into_raw(my_speed);
 
    unsafe {
        let my_speed_two: Box = Box::from_raw(my_speed_ptr);
        let static_ref: &'static mut String = Box::leak(my_speed_two);
       
        (*static_ref).push_str(" World!");
       
        (static_ref,my_speed_ptr)
    }
}
fn main(){
    let s = String::from("Hello ");
    let (static_str,my_speed_ptr):(&'static str,*mut String) = get_static(s);
    println!("{}",static_str);
    unsafe {
         drop(Box::from_raw(my_speed_ptr));
    }
}

Передача String между потоками

optimizing-immutable-strings-in-rust

Для оптимизации не мутирующей строки при передачи между потоками нет смысла клонировать строку

Лучше клонировать Arc или &str


use std::sync::Arc;
fn main(){
    let text: String = get_string_from_somwhere();
    // мы можем использовать синтаксис `Arc::from`
    let owned_reference: Arc<_> = text.into();
    todo!("spawn threads here");
}

Vec<char> vs String

Vec<char> и String имеют разные преимущества в зависимости от задачи. Вот сравнение:

1. Производительность операций

Vec<char> лучше для:

fn main() {
    let text = "Hello 世界 🦀";
    let chars_vec: Vec<char> = text.chars().collect();
    let string = text.to_string();
    
    // Доступ по индексу - O(1) vs O(n)
    println!("3-й символ: {:?}", chars_vec.get(3)); // Быстро
    // У String доступ по индексу символа медленный
    
    // Замена символа по индексу
    let mut chars = chars_vec.clone();
    chars[3] = 'X'; // Быстро
    println!("После замены: {:?}", chars);
    
    // Удаление символа по индексу
    chars.remove(3); // Быстро
    println!("После удаления: {:?}", chars);
    
    // Вставка символа
    chars.insert(3, '!'); // Быстро
    println!("После вставки: {:?}", chars);
}

String лучше для:

fn main() {
    let s1 = "Hello".to_string();
    let s2 = " World".to_string();
    
    // Конкатенация - очень эффективна
    let combined = s1 + &s2;
    println!("{}", combined);
    
    // Срезы байт
    let text = "Hello 世界";
    let byte_slice = &text[0..5]; // Быстро
    println!("{}", byte_slice);
}

2. Использование памяти

fn main() {
    let text = "Hello";
    let chars_vec: Vec<char> = text.chars().collect();
    let string = text.to_string();
    
    println!("String размер: {} байт", std::mem::size_of_val(&string));
    println!("Vec<char> размер: {} байт", std::mem::size_of_val(&chars_vec));
    println!("String capacity: {}", string.capacity());
    println!("Vec<char> capacity: {}", chars_vec.capacity());
    
    // char всегда 4 байта, но String хранит UTF-8
    println!("'A' как char: {} байт", std::mem::size_of::<char>());
    println!("'A' в UTF-8: {} байт", 'A'.len_utf8());
    println!("'世' как char: {} байт", std::mem::size_of::<char>());
    println!("'世' в UTF-8: {} байт", '世'.len_utf8());
}

3. Практические сценарии

Когда использовать Vec<char>:

fn reverse_string(text: &str) -> String {
    let chars: Vec<char> = text.chars().collect();
    chars.iter().rev().collect() // Простое реверсирование
}

fn process_individual_chars(text: &str) {
    let chars: Vec<char> = text.chars().collect();
    
    // Множественные операции с индексами
    for i in 0..chars.len() {
        if i % 2 == 0 {
            // Легко работать с конкретными позициями
            println!("Символ на позиции {}: {}", i, chars[i]);
        }
    }
    
    // Фильтрация с сохранением индексов
    let filtered: Vec<(usize, char)> = chars.iter()
        .enumerate()
        .filter(|(_, &c)| c.is_alphabetic())
        .map(|(i, &c)| (i, c))
        .collect();
}

fn main() {
    println!("Реверс: {}", reverse_string("Hello 世界"));
    process_individual_chars("Hello123");
}

Когда использовать String:

fn build_string_parts() -> String {
    let mut result = String::new();
    
    // Эффективное построение из частей
    result.push_str("Hello");
    result.push(' ');
    result.push_str("世界");
    result.push(' ');
    result.push('🦀');
    
    result
}

fn string_operations() {
    let text = "Hello World 世界".to_string();
    
    // Эффективные операции со строками
    let lowercase = text.to_lowercase();
    let replaced = text.replace("World", "Rust");
    let substring = &text[6..11]; // "World"
    
    println!("{}", lowercase);
    println!("{}", replaced);
    println!("{}", substring);
}

fn main() {
    println!("{}", build_string_parts());
    string_operations();
}

4. Сравнение производительности

use std::time::Instant;

fn main() {
    let text = "a".repeat(10000);
    
    // Тест String
    let start = Instant::now();
    let string = text.clone();
    for _ in 0..1000 {
        let _ = string.chars().nth(5000); // Медленно
    }
    println!("String access: {:?}", start.elapsed());
    
    // Тест Vec<char>
    let start = Instant::now();
    let chars: Vec<char> = text.chars().collect();
    for _ in 0..1000 {
        let _ = chars.get(5000); // Быстро
    }
    println!("Vec<char> access: {:?}", start.elapsed());
}

5. Вывод: когда что использовать

Vec<char> лучше когда:

  • Частый доступ по индексу к отдельным символам
  • Множественные модификации (вставка, удаление, замена)
  • Алгоритмы, требующие случайного доступа к символам
  • Работа с символами как с отдельными сущностями

String лучше когда:

  • Чтение и вывод текста
  • Конкатенация и построение строк
  • Работа со срезами байт
  • Использование стандартных методов строк (поиск, замена и т.д.)
  • Экономия памяти (особенно для ASCII текста)

Гибридный подход:

#![allow(unused)]
fn main() {
fn efficient_processing(text: &str) -> String {
    // Конвертируем в Vec<char> для сложных операций
    let mut chars: Vec<char> = text.chars().collect();
    
    // Быстрые операции с индексами
    for i in 0..chars.len() {
        if i % 3 == 0 {
            chars[i] = chars[i].to_uppercase().next().unwrap();
        }
    }
    
    // Возвращаем как String для дальнейшего использования
    chars.into_iter().collect()
}
}

Методы std::string::String

rust-cheatsheet

  • as_str() - преобразует String в &str
  • as_mut_str() - Преобразует String в изменяемый &mut str

  • String::new() - изначально пусто, но по мере роста строки она увеличивается тратя время
  • String::with_capacity(25) - изначально увеличена на N и будет расти, нет необходимости делать это по мере роста
  • String::from_str() - преобразует &str в String
  • format!() - макрос создает форматированную строку
  • from_raw_parts() - unsafe Создает String из raw данных (указатель, длина, емкость).
  • into_raw_parts() - Возвращает необработанный указатель на raw данные (указатель, длина, емкость)

  • len() - Возвращает количество байт этого объекта String, а не количества символов или графемах
  • "".chars().count() - количество символов
  • is_empty() - Возвращает, true если String длина равна нулю, в false противном случае

  • push() - Добавляет символ char в конец String
  • push_str() - Добавляет заданный фрагмент &str в конец String
  • write_str() - дописывает в конец строки (благодаря trait std::fmt::Write)
  • insert() - Вставляет символ char в String по байтовой позиции
  • insert_str() - Вставляет &str в String по байтовой позиции
  • extend(iter) - дописывает в конец объекты, порождаемые итератором (благодаря trait std::iter::Extend)

  • capacity() - Возвращает выделенную емкость в куче String
  • reserve() - Увеличивает емкости String если это нужно
  • reserve_exact() - Увеличивает емкость этой строки на N байт чем ее длина.
  • try_reserve() - Пытается зарезервировать емкость
  • try_reserve_exact() - Пытается зарезервировать минимальную емкость
  • shrink_to_fit() - Уменьшает вместимость String до ее длины
  • shrink_to() - Емкость останется не меньше длины и предоставленного значения.

  • from_utf8() - Преобразует vector байтов в String
  • from_utf8_lossy() - Преобразует фрагмент байтов в String, включая недопустимые символы
  • from_utf8_unchecked() - Преобразует vector байтов в String без проверки валидности UTF-8
  • from_utf16() - Декодирует vector UTF-16 в String
  • from_utf16_lossy() - Декодирует vector UTF-16 в String с заменой недопустимых символов
  • as_bytes() - Возвращает байтовый Vec<u8>

  • into_bytes() - преобразует String в Vec<u8>
  • as_mut_vec() - Возвращает &mut Vec<u8> на содержимое этой строки
  • into_boxed_str() - Преобразует строку в Box<str>

  • truncate() - Сокращает эту строку до указанной длины.
  • clear() - Усекает эту строку, удаляя все содержимое. len = 0 (емкость не трогает)
  • retain() - Фильтрует строку удовлетворяющую предикату
  • pop() - Удаляет последний символ из буфера строк и возвращает его.
  • drain() - Итератор удаляющий из String диапазон и возвращает их как chars
  • remove(n) - Удаляет char из String по байтовой позиции и возвращает его
  • remove_matches() - Удалите все совпадения шаблона
  • replace_range() - Удаляет указанный диапазон в строке и заменяет его заданной строкой.

  • split_off() - разделяет сроку на две по байту, первая часть остается в исходной строке mut
  • as_str() - преобразует String в &str
  • as_mut_str() - Преобразует String в изменяемый &mut str

fn main(){
    let s = String::from("foo");
    assert_eq!("foo", s.as_str());
    let mut s = String::from("foobar");
    let s_mut_str:&mut str = s.as_mut_str();
    s_mut_str.make_ascii_uppercase();
    assert_eq!("FOOBAR", s_mut_str);
}
  • String::new() - изначально пусто, но по мере роста строки она увеличивается тратя время
  • String::with_capacity(25) - изначально увеличена на N и будет расти, нет необходимости делать это по мере роста
  • String::from_str() - преобразует &str в String
  • format!() - макрос создает форматированную строку
  • Trait std::string::ToString - Трейт для преобразования значения в String (для всех Display автоматически получаем ToString)

fn main(){
// Отличие способа выделение памяти емкости
// Если известна примерная емкость заранее то лучьше with_capacity
    let mut s = String::new();
    let mut s = String::with_capacity(25);
    println!("{}", s.capacity());
    for _ in 0..8 {
        s.push_str("hello");
        println!("{}", s.capacity());
    }
// ------------------------------------------------------------
// Конкатенация строк через оператор +
    let hello = String::from("Hello");
    let world = String::from("world");
    let rust = " in Rust";

    let sentence = hello + " " + &world + rust;
// ------------------------------------------------------------
use std::str::FromStr;
let s:&str = "hello";
let s:String = String::from_str(s).unwrap();
// ------------------------------------------------------------
    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s:String = format!("{s1}-{s2}");
// ------------------------------------------------------------
// ToString for &str
    let s1:String = "tic".to_string();
    let s2:String = ToString::to_string("tac");
}
  • len() - количество байт строки
  • std::mem::size_of_val(x) - количество байт значения x
  • "".chars().count() - количество символов
  • std::mem::size_of::<u8>() - размер типа в байтах

fn main(){
    let a = String::from("foo");
    a.len()

    let s = String::from("hello");
    assert_eq!(5, s.len() * std::mem::size_of::()); //  размер типа в байтах

// Метод .len() типа String или &str возвращает длину строки, измеряемую в байтах, а не в символах:
    assert_eq!("ಠ_ಠ".len(), 7);
    assert_eq!("ಠ_ಠ".chars().count(), 3);
}

т.е. хоть char имеет 4 байта, но строка оптимизирует хранение символов и удаляет пустые байты


fn main() {
    println!("Size of a char: {}", std::mem::size_of::()); // 4 байта
    println!("Size of a: {}", "a".len()); // 1 байта
    println!("Size of ß: {}", "ß".len()); // 2 байта
    println!("Size of 国: {}", "国".len()); // 3 байта или print!("{}",std::mem::size_of_val("国"));
    println!("Size of 𓅱: {}", "𓅱".len()); // 4 байта
}

is_empty() - Возвращает true, если эта строка имеет длину 0


fn main(){
    let mut v = String::new();
    assert!(v.is_empty());
}
  • Конкатенация String + &String + &str
  • push('b') - добавить char
  • push_str("hello") - добавить &str
  • write_str() - дописывает в конец строки (благодаря trait std::fmt::Write)

fn main(){
// Конкатенация
    let a:String = "hello".to_string();
    let b:&str = " world";
    let c:String = a + b;
    assert_eq!("hello world",c);

    let c:String = a + &a;
    let c:String = a + "-" + &b.to_owned() + "-" + &a;
// ----------------------------------------
    let mut s = String::from("foo");
    s.push('b');
// ----------------------------------------
    let mut s = String::from("foo");
    s.push_str("hello");
// ----------------------------------------
    use std::fmt::Write;
    let mut s1:String = "tic".to_string();
    let _ = s1.write_str("tac");
    let _ = s1.write_char('t');
    let _ = s1.write_fmt(format_args!("{}","ac"));
    assert_eq!("tictactac".to_string(),s1);
}
  • insert() - Вставляет символ в эту строку в позицию байта. O(n) Это операция O(n), поскольку она требует копирования каждого элемента в буфере.
  • insert_str() - Вставляет фрагмент строки в эту строку в позиции байта. O(n) Это операция O(n), поскольку она требует копирования каждого элемента в буфере.
  • extend(iter) - дописывает в конец объекты, порождаемые итератором (благодаря trait std::iter::Extend)

fn main(){
    let mut s = String::with_capacity(3);
    s.insert(0, 'f');
    s.insert(1, 'o');
    s.insert(2, 'o');
    assert_eq!("foo", s);

    let mut s = String::with_capacity(3);
    s.insert(0, 'т');
    s.insert(0, 'е');
    s.insert(0, 'н');
    assert_eq!("нет", s);
}


fn main(){
    let mut s = String::from("bar");
    s.insert_str(0, "foo");
    assert_eq!("foobar", s);
}

string.extend(iter) дописывает в конец объекты, порождаемые итератором iter.

Итератор может порождать значения типа char , str или String .

Все это реализации трейта std::iter::Extend для типа String

Extend<&'a char>
Extend<&'a str>
Extend<Box<str, Global>>
Extend<Cow<'a, str>>
Extend<String>
Extend<char>

fn main(){
    let mut s = "con".to_string();
    s.extend("tri but ion".split_whitespace());
    assert_eq!(s, "contribution");

    let mut s = "".to_string();
    s.extend("hello".chars());
    assert_eq!(s, "hello");

    let mut msg = String::from("abc");
    let iter_extend = ['d', 'e', 'f'].iter();
    msg.extend(iter_extend);
    assert_eq!("abcdef", &msg);
}

Capacity manipulation

  • capacity() - Возвращает выделенную емкость в куче String
  • reserve() - Увеличивает емкости String если это нужно
  • reserve_exact() - Увеличивает емкость этой строки на N байт чем ее длина.
  • shrink_to_fit() - Уменьшает вместимость String до ее длины

fn main(){
    let s = String::with_capacity(10);
    assert!(s.capacity() >= 10);
    
// Емкость может быть увеличина на N байт
    let mut s = String::new();
    s.reserve(5);
    println!("capacity={}",s.capacity());// 5

    let mut s = String::new();
    s.reserve_exact(6);
    s.push_str("1234567");
    println!("capacity={}",s.capacity());// 12

    let mut s = String::from("foo");
    s.reserve(100);
    println!("capacity={}",s.capacity());// 103
    s.shrink_to_fit();
    println!("capacity={}",s.capacity());// 3
}
  • from_utf8() - Преобразует c проверкой валидности байта из вектора байт Vec<u8> в &str<u8> Лучше std::str::from_utf8(&file_content) он не завладевает
  • from_utf8_unchecked() - Преобразует без проверки валидности байта для строки
  • from_utf8_lossy() - заменит любые недопустимые последовательности UTF-8
  • as_bytes() - Преобразует строку &str<u8> в вектор Vec<u8> Однако не все байтовые байты действительны для строк

enum.Cow


fn main(){
   let sparkle_heart = vec![240, 159, 146, 150];
    unsafe{ println!("{}", String::from_utf8_unchecked(sparkle_heart)); }
    let mut sparkle_heart = vec![240, 159, 146, 150,999999999];
    let mut sparkle_heart = vec![240, 159, 146, 150];
     //if let Ok(i) = String::from_utf8(sparkle_heart){ println!("{}",i); }
    match String::from_utf8(sparkle_heart) {
        Ok(i) => {
            println!("{}", i);
            println!("as_bytes {:?}",i.as_bytes());// [240, 159, 146, 150]
        },
        Err(e) => println!("Error:{}", e)
    }
    let sparkle_heart = vec![240, 159, 146, 150];
    let sparkle_heart:std::borrow::Cow  = String::from_utf8_lossy(&sparkle_heart);
    println!("from_utf8_lossy={}", sparkle_heart);
    //некоторые недопустимые байты
    let input = b"Hello \xF0\x90\x80World";
    let output:std::borrow::Cow = String::from_utf8_lossy(input);
    println!("from_utf8_lossy={}", output);
// ---------------------------------------------------------------------------

    assert_eq!("111".to_owned(),String::from_utf8((&[49,49,49]).to_vec()).unwrap()); // через владение
    assert_eq!("111" ,std::str::from_utf8(&[49,49,49]).unwrap()); // без владения
}


fn main(){
    let b:&[u8] = b"some bytes";
    let b:Vec = vec![115, 111, 109, 101, 32, 98, 121, 116, 101, 115];
    let b:&[u8] = "some bytes".as_bytes();
    let b:Vec = String::from("some bytes").into_bytes();
    println!("{:?}",b);// [115, 111, 109, 101, 32, 98, 121, 116, 101, 115]

    println!("{}",if let Ok(result)=std::str::from_utf8(&b){result}else {""});
 
}
  • into_bytes() - преобразует String в вектор байтов
  • as_mut_vec() - Возвращает mut ссылку Vec<u8> на содержимое этой строки
  • into_boxed_str() - Преобразует эту строку в Box<str>

fn main(){
    let b:&[u8] = b"some bytes";
    let b:Vec = vec![115, 111, 109, 101, 32, 98, 121, 116, 101, 115];
    let b:&[u8] = "some bytes".as_bytes();
    let b:Vec = String::from("some bytes").into_bytes();
    println!("{:?}",b);// [115, 111, 109, 101, 32, 98, 121, 116, 101, 115]
   // обратно в &str
   let result:&str = std::str::from_utf8(&b).unwrap();
}


fn main(){
   let mut s = String::from("hello");
    unsafe {
        let vec = s.as_mut_vec();
        assert_eq!(&[104, 101, 108, 108, 111][..], &vec[..]);
        vec.reverse();
    }
    assert_eq!(s, "olleh");
}


fn main(){
    use std::boxed::Box;
    let s = String::from("hello");
    let b:Box = s.into_boxed_str();

     let s = String::from("hello");
     let my_string:Box = Box::new(s);

    print_if_string(my_string);

    fn print_if_string(value: Box) {
        if let Ok(string) = value.downcast::() {
            println!("String ({}): {}", string.len(), string);
        }
    }
}

Clearing

  • truncate() - Сокращает эту строку до указанной длины.
  • clear() - Усекает эту строку, удаляя все содержимое. len = 0 Хотя это означает, что String будет иметь длину нуля, она не затрагивает его емкость.
  • retain() - Фильтрует строку, удовлетворяющую предикату

fn main(){
    let mut s = String::from("привет");
    // s.truncate(3);// panic! если разорвать символ u8
    s.truncate(2);
    s.shrink_to_fit();
    println!("{} capacity={}",s,s.capacity());

    let mut s = String::from("foo");
    s.clear();
    assert!(s.is_empty());
    assert_eq!(0, s.len());
    assert_eq!(3, s.capacity());  

   let mut s = String::from("f_o_ob_ar");
    s.retain(|c| c != '_');
    assert_eq!(s, "foobar");
}
  • pop() - Удаляет последний символ из буфера строк и возвращает его.
  • drain() - Итератор удаляющий из String диапазон и возвращает их как chars Примечание. Диапазон элементов удаляется, даже если итератор не потребляется до конца.
  • remove(n) - Удаляет символ из этой строки в позиции байта и возвращает его. За O(n) Это операция O(n), так как она требует копирования каждого элемента в буфере. Вызывает panic!
  • remove_matches() - Удалите все совпадения шаблона
  • replace_range() - Удаляет указанный диапазон в строке и заменяет его заданной строкой.

fn main(){
// Возвращает None, если эта строка пуста.
    let mut s = String::from("fo");
    assert_eq!(s.pop(), Some('o'));
    assert_eq!(s.pop(), Some('f'));
    assert_eq!(s.pop(), None);
}


fn main(){
    let mut s = String::from("α is alpha, β is beta");
    let beta_offset = s.find('β').unwrap_or(s.len());

// Удалите диапазон до тех пор, пока β из строки
    let t: String = s.drain(0..beta_offset).collect();
    assert_eq!(t, "α is alpha, ");
    assert_eq!(s, "β is beta");

// Полный диапазон очистки строки
    s.drain(..);
    assert_eq!(s, "");
}


fn main(){
    let mut s = String::from("нет");
    assert_eq!(s.remove(0), 'н');
    println!("{} capacity={}",s,s.capacity());// ет capacity=6
    assert_eq!(s.remove(0), 'е');
    println!("{} capacity={}",s,s.capacity());// т capacity=6
    assert_eq!(s.remove(0), 'т');

    let mut s = String::from("fox");
    assert_eq!(s.remove(2), 'x');
    assert_eq!(s.remove(1), 'o');
    assert_eq!(s.remove(0), 'f');
    println!("{} capacity={}",s,s.capacity());
}


fn main(){
    #![feature(string_remove_matches)]
    let mut s = String::from("Trees are not green, the sky is not blue.");
    s.remove_matches("not ");
    assert_eq!("Trees are green, the sky is blue.", s);
}


fn main(){
    let mut s = String::from("α is alpha, β is beta");
    let beta_offset = s.find('β').unwrap_or(s.len());

    // Замените диапазон до β  
    s.replace_range(..beta_offset, "Α is capital alpha; ");
    assert_eq!(s, "Α is capital alpha; β is beta");
}

split_off() - разделяет сроку на две по байту, первая часть остается в исходной строке mut


fn main(){
    let mut hello = String::from("Hello, World!");
    let world = hello.split_off(7);
    assert_eq!(hello, "Hello, ");
    assert_eq!(world, "World!");
}

Благодаря Deref<Target=str>

chars() - Возвращает итератор по chars фрагмента строки


fn main(){
    let s = String::from("love: ❤️");
    let v: Vec = s.chars().collect();
}

Unix/Windows независимые строки от платформы c отсутствующей гарантией валидной строки UTF-8

  • new
  • as_os_str
  • into_string
  • push
  • with_capacity
  • clear
  • capacity
  • reserve
  • reserve_exact
  • shrink_to_fit
  • shrink_to
  • into_boxed_os_str

fn main(){
    use std::ffi::{OsString, OsStr};

    let os_string = OsString::from("foo");
    let os_str = OsStr::new("foo");
    assert_eq!(os_string.as_os_str(), os_str);
}


fn main(){
    use std::ffi::OsString;

    let os_string = OsString::from("foo");
    let string:String = os_string.into_string();
    assert_eq!(string, Ok(String::from("foo")));
}


fn main(){
    let mut os_string = OsString::from("foo");
    os_string.push("bar");
    assert_eq!(&os_string, "foobar");
}

CString и CStr - это специальные типы в Rust для работы со строками, совместимыми с языком C. Они обеспечивают безопасное взаимодействие с FFI (Foreign Function Interface). Не гарантируют валидную UTF-8 строку

Ключевые особенности:

  • Null-terminated: Всегда заканчиваются нулевым байтом для безопасной передачи строк для C
  • Без нулей внутри: Не могут содержать нулевые байты в середине
  • FFI-безопасность: Гарантируют корректное представление в памяти
  • Владение: CString владеет данными, CStr заимствует

Используйте эти типы при работе с иностранными функциями (C библиотеками) для обеспечения безопасности и корректности!

CString - владеющая C-совместимая строка

use std::ffi::CString;

fn main() {
    // Из &str (проверяет на нулевые байты)
    let cstring1 = CString::new("Hello World").unwrap();
    
    // Из Vec<u8> (также проверяет на нулевые байты)
    let bytes = vec![72, 101, 108, 108, 111]; // "Hello"
    let cstring2 = CString::new(bytes).unwrap();
    
    // Использование with_capacity
    let mut cstring3 = CString::new("Hello").unwrap();
    
    // Добавление в конец
    cstring3.push_bytes(b" World");
}

Ошибки при создании:

use std::ffi::CString;

fn main() {
    // Ошибка: строка содержит нулевой байт
    match CString::new("Hello\0World") {
        Ok(s) => println!("Успех: {:?}", s),
        Err(e) => println!("Ошибка: {}", e), // Сработает это
    }
    
    // Правильно - экранирование нулевого байта
    let bytes = b"Hello\0World".to_vec();
    let cstring = CString::new(bytes).unwrap();
}

CStr - заимствованная C-совместимая строка

Создание и использование CStr:

use std::ffi::{CStr, CString};

fn main() {
    // Создаем CString
    let cstring = CString::new("Hello Rust").unwrap();
    
    // Получаем CStr ссылку
    let cstr: &CStr = cstring.as_c_str();
    
    // Из сырого указателя (unsafe)
    let ptr = cstring.as_ptr();
    unsafe {
        let cstr_from_ptr = CStr::from_ptr(ptr);
        println!("Из указателя: {:?}", cstr_from_ptr);
    }
    
    // Из байт с нулем в конце
    let bytes_with_nul = b"Hello\0";
    let cstr_from_bytes = CStr::from_bytes_with_nul(bytes_with_nul).unwrap();
    println!("Из байт: {:?}", cstr_from_bytes);
}

Взаимодействие с Rust строками:

use std::ffi::{CString, CStr};

fn main() {
    // String -> CString
    let rust_string = String::from("Hello Rust");
    let cstring = CString::new(rust_string).unwrap();
    
    // CString -> String
    let back_to_string = cstring.to_string_lossy().into_owned();
    println!("{}", back_to_string);
    
    // &str -> CString
    let rust_str = "Hello World";
    let cstring_from_str = CString::new(rust_str).unwrap();
    
    // CStr -> &str
    let cstr = cstring_from_str.as_c_str();
    match cstr.to_str() {
        Ok(s) => println!("Valid UTF-8: {}", s),
        Err(_) => println!("Invalid UTF-8, используем lossy конверсию"),
    }
    
    // Lossy конверсия (для не-UTF8 данных)
    let lossy_str = cstr.to_string_lossy();
    println!("Lossy: {}", lossy_str);
}

Практические примеры с FFI

Вызов C функции:

use std::ffi::{CString, CStr};
use std::os::raw::c_char;

// Предположим, что это внешняя C функция
extern "C" {
    fn strlen(s: *const c_char) -> usize;
    fn strcpy(dest: *mut c_char, src: *const c_char) -> *mut c_char;
}

fn main() {
    // Создаем C-совместимую строку
    let cstring = CString::new("Hello FFI").unwrap();
    
    unsafe {
        // Передаем в C функцию
        let length = strlen(cstring.as_ptr());
        println!("Длина строки: {}", length);
        
        // Копирование строки
        let mut buffer = vec![0u8; length + 1];
        let dest = buffer.as_mut_ptr() as *mut c_char;
        strcpy(dest, cstring.as_ptr());
        
        // Чтение результата
        let result = CStr::from_ptr(dest);
        println!("Скопировано: {:?}", result);
    }
}

Работа с не-UTF8 данными:

use std::ffi::{CString, CStr};

fn main() {
    // Строка с не-UTF8 последовательностью
    let bytes = vec![0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x80, 0x81]; // "Hello" + invalid UTF-8
    
    let cstring = CString::new(bytes).unwrap();
    let cstr = cstring.as_c_str();
    
    // to_str() вернет ошибку
    if let Err(e) = cstr.to_str() {
        println!("Ошибка UTF-8: {}", e);
    }
    
    // to_string_lossy() работает всегда
    let lossy = cstr.to_string_lossy();
    println!("Lossy конверсия: {}", lossy); // "Hello��"
}

Безопасные обертки для FFI

use std::ffi::{CString, CStr};
use std::os::raw::c_char;

// Безопасная обертка вокруг C функции
fn safe_strlen(s: &str) -> Result<usize, std::ffi::NulError> {
    let c_string = CString::new(s)?;
    
    unsafe {
        Ok(unsafe_strlen(c_string.as_ptr()))
    }
}

// Небезопасная внешняя функция
extern "C" {
    fn unsafe_strlen(s: *const c_char) -> usize;
}

// Функция для получения строки из C
unsafe fn get_string_from_c(ptr: *const c_char) -> Option<String> {
    if ptr.is_null() {
        return None;
    }
    
    let c_str = CStr::from_ptr(ptr);
    c_str.to_str().ok().map(|s| s.to_owned())
}

fn main() {
    // Безопасное использование
    match safe_strlen("Hello World") {
        Ok(len) => println!("Длина: {}", len),
        Err(e) => println!("Ошибка: {}", e),
    }
}

Работа с массивами строк (char**)

use std::ffi::{CString, CStr};
use std::os::raw::c_char;
use std::ptr;

fn main() {
    // Создание массива C строк для передачи в C функцию
    let args = vec!["program_name", "--help", "-v"];
    
    // Конвертируем в CString
    let c_args: Vec<CString> = args.iter()
        .map(|&s| CString::new(s).unwrap())
        .collect();
    
    // Создаем массив указателей
    let mut c_ptrs: Vec<*const c_char> = c_args.iter()
        .map(|s| s.as_ptr())
        .collect();
    c_ptrs.push(ptr::null()); // NULL terminator для массива
    
    // Теперь c_ptrs можно передать в C функцию как char**
    println!("Массив содержит {} элементов", c_ptrs.len() - 1);
}

str

&std::str

primitive.str

str - основной строковый тип состоит из slice байт [u8] который должен складываться в валидную UTF-8 строку

Так как slice это ссылка на данные поэтому используется тип &str

&str - это широкий указатель, содержит адрес первого элемента последовательности на срез slice строки String и размер этой последовательности.

Размещается на stack

&str - два значения: адрес str и его длина

Ссылка на последовательность байт UTF-8, имеют фиксированный размер и не могут быть изменены (могут, но без увеличения размера) и существуют на протяжении всей программы (static)

Каждый символ в UTF-8 занимает от 1-4 байт

| 72 | 101 | 108 | 108 | 111 | 32 | 208 | 188 | 208 | 184 | 209 | 1258|
| H  | e   | l   | l   | o   |    |     М     |    И      |     Р     |

fn main() {
    let text = "W М🦀"; // Латинская W, кириллическая М, греческая альфа, краб эмодзи
    
    for (index, ch) in text.char_indices() {
        let size = ch.len_utf8();
        let bytes: Vec<u8> = text[index..index+size].as_bytes().to_vec();
        
        println!("Символ '{}':", ch);
        println!("  - Позиция в строке: {}", index);
        println!("  - Размер в байтах: {}", size);
        println!("  - Байты: {:?}", bytes);
        println!("  - Кодовая точка: U+{:04X}", ch as u32);
        println!("  - Битное представление:");
        
        for (i, byte) in bytes.iter().enumerate() {
            let binary = format!("{:08b}", byte);
            let grouped = format!("{} {}", &binary[0..4], &binary[4..8]);
            println!("    Байт {}: {} (0x{:02X})", i, grouped, byte);
        }
        println!();
    }
}

У символов латинского алфавита 1 байт (8 бит) на символ:

Символ 'W':
  - Позиция в строке: 0
  - Размер в байтах: 1
  - Байты: [87]
  - Кодовая точка: U+0057
  - Битное представление:
    Байт 0: 0101 0111 (0x57)

Символ ' ':
  - Позиция в строке: 1
  - Размер в байтах: 1
  - Байты: [32]
  - Кодовая точка: U+0020
  - Битное представление:
    Байт 0: 0010 0000 (0x20)

В кириллице 2 байта на символ:

Символ 'М':
  - Позиция в строке: 2
  - Размер в байтах: 2
  - Байты: [208, 156]
  - Кодовая точка: U+041C
  - Битное представление:
    Байт 0: 1101 0000 (0xD0)
    Байт 1: 1001 1100 (0x9C)

В эмодзи 4 байта:

Символ '🦀':
  - Позиция в строке: 4
  - Размер в байтах: 4
  - Байты: [240, 159, 166, 128]
  - Кодовая точка: U+1F980
  - Битное представление:
    Байт 0: 1111 0000 (0xF0)
    Байт 1: 1001 1111 (0x9F)
    Байт 2: 1010 0110 (0xA6)
    Байт 3: 1000 0000 (0x80)
fn main() {
    let text = "Hi Мир 世界 🦀";
    
    for (byte_pos, ch) in text.char_indices() {
        println!("Байтовая позиция: {}, Символ: '{}'", byte_pos, ch);
    }
}
Байтовая позиция: 0, Символ: 'H'
Байтовая позиция: 1, Символ: 'i'
Байтовая позиция: 2, Символ: ' '
Байтовая позиция: 3, Символ: 'М'
Байтовая позиция: 5, Символ: 'и'
Байтовая позиция: 7, Символ: 'р'
Байтовая позиция: 9, Символ: ' '
Байтовая позиция: 10, Символ: '世'
Байтовая позиция: 13, Символ: '界'
Байтовая позиция: 16, Символ: ' '
Байтовая позиция: 17, Символ: '🦀'

Создание валидной UTF-8 строки

Так как &str это ссылка на валидную UTF-8 строку, то любые способы создания &str из набора байт являются unsafe либо safe метод с проверкой from_utf8 При этом аллокации нет, просто теперь эта последовательность байт проверенна и является валидной UTF-8 строкой

const BYTES: [u8; 11] = [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100];

fn main() {
    // unsafe метод - без проверки на валидность UTF-8
    // std::str::from_utf8_unchecked() - unsafe метод без проверки
    let text: &str = unsafe {
        std::str::from_utf8_unchecked(&BYTES)
    };
    assert_eq!(text, "Hello World");

    // Использование указателей (более низкоуровневый подход)
    let text: &str = unsafe {
        let ptr = &BYTES as *const u8;
        let slice = std::slice::from_raw_parts(ptr, BYTES.len());
        std::str::from_utf8_unchecked(slice)
    };
    assert_eq!(text, "Hello World");
    
    // safe метод - с проверкой на валидность UTF-8
    // std::str::from_utf8() - безопасный метод с проверкой
    let text: &str = std::str::from_utf8(&BYTES).unwrap();
    assert_eq!(text, "Hello World");
}

//std::str::from_utf8_mut() - для mutable байт
//std::str::from_utf8_mut_unchecked() - unsafe mutable вариант

Тип str - динамическая строка (но неполноценная)

Тип &str - динамическая строка с известной компилятору длиной

&str - это также называется срезом строки.

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

Также обратите внимание, что перед ним &str стоит &, потому что вам нужна ссылка для использования str по той же ранее обсуждавшейся причине: стек должен знать размер, а str может быть любой длины. Поэтому мы обращаемся к нему с помощью &, ссылки. Компилятор знает размер указателя ссылки, и затем он может использовать, & чтобы найти, где str находятся данные, и прочитать их. Кроме того, поскольку вы используете & для взаимодействия с str, вы не владеете им.

Давайте снова рассмотрим причину использования a &для strs, чтобы убедиться, что мы понимаем. A str — это тип с динамическим размером. Динамический размер означает, что размер может быть разным. Например, два имени, ("자우림" и "Adrian Fahrenheit Țepeș"), не имеют одинакового размера. т.е. эти типы не могут существовать так как они имеют разный размер, который не известен без использования &

let name_1:str = "자우림" 
let name_2:str = "Adrian Fahrenheit Țepeș" 

Вот почему нам нужен & потому что он создает указатель, а Rust знает размер указателя. Таким образом, в стек попадает только указатель. Если бы мы написали str, Rust не знал бы, что делать, потому что он не знает размер.

'static

'static время жизни длится до конца программы, поэтому, если она переживет его, нет ли у вас утечки памяти? Объяснение заключается в том, что именно тип, а не значение, должен пережить 'static время жизни, и значение может быть уничтожено до того, как его тип перестанет быть действительным.

Когда мы говорим, что значение равно 'static, это означает лишь то, что было бы правильно хранить это значение всегда.

'static str хранит указатель на строку в stack, но сами данные хранятся статично в бинарном скомпилированном файле программы пожизненно Так же и String берет строки из бинарного файла как и str, но выделяет для них память в куче и копирует их туда

let s = "Hello, world!";

Тип s здесь является &str срезом, указывающим на конкретное место в бинарном файле программы. Это также объясняет, почему строковый литерал является неизменяемым, потому что тип &str это неизменяемая ссылка.

let b:String = String::from("Hello, world!");

эта строка была скопирована из того же бинарного файла и помещена в кучу т.е. для нее выделили новое место хранения

data memory (память данных) - для данных фиксированного размера и статических данных (доступные в любой момент времени выполнения программы). Рассмотрим текст в вашей программе (пример строка "Hello World!"). Эта строка является набором байт, которые нельзя изменить и можно только считать, поэтому они могут сохраняться в данном регионе. Компиляторы делают очень много оптимизаций с таким видом данных. Этот регион памяти считается очень быстрым, так как местоположение данные известно и фиксировано заранее.

&str.to_owned()

&str.to_string()

to_owned преобразует заимствованные данные в собственные.

Вы всегда должны использовать to_owned()

to_string() - это общее преобразование в String из любого типа, реализующего свойство ToString.

Он использует функции форматирования и, следовательно, может сделать несколько распределений и запустить гораздо больше кода, чем простой to_owned(), который просто выделяет буфер и копирует литерал в буфер обычно путем клонирования.


fn main(){
    let s:&str = "tac";
    let res:String = s.to_owned();
}

Мутация &str (Все методы с &mut self это для String)


fn main(){
// Получить &mut str:
 let mut buf:String = String::from("hello");
 let s:&mut str = buf.get_mut(0..).unwrap();
 let s:&mut str = buf.as_mut();
 let s:&mut str = buf.borrow_mut();
}


fn main(){
   // Можно уменьшить размер среза, но не увеличить:
   let mut rust:&str = "hello";
   rust = rust.trim_matches('o');
   rust = rust.strip_prefix("h").unwrap();
   assert_eq!("ell",rust);

   // Для изменения содержимого строки, можно хранить ее в виде среза 
   // Без увеличения размера и соблюдения длины символа, так как кириллица занимает 2 байта в отличии от 1 байта латиницы
   let bytes:&mut [u8] = &mut [104, 101, 108, 108, 111]; // hello
   bytes[2]=b'h';
   bytes[3]=104u8;
   assert_eq!("hehho",std::str::from_utf8(&bytes).unwrap());
   let mut rust = std::str::from_utf8(&bytes).unwrap(); 
   rust = rust.trim_matches('o');
   rust = rust.strip_prefix("h").unwrap();
   assert_eq!("ehh",rust); 

    // Заменим два символа латиницы `ll` на один кириллицы `М`:
    let bytes:&mut [u8] = &mut [104, 101, 108, 108, 111]; // hello
    bytes[2]=208u8;
    bytes[3]=156u8;
    assert_eq!("heМo",std::str::from_utf8(&bytes).unwrap());
    let mut rust = std::str::from_utf8(&bytes).unwrap(); 
    rust = rust.trim_matches('o');
    rust = rust.strip_prefix("h").unwrap();
    assert_eq!("eМ",rust); 

}

From полезен для выполнения преобразований между строками.

impl From<&mut str> for String
impl From<&str> for String
impl From<&str> for Arc<str>
impl From<&str> for Rc<str>
impl From<&str> for Vec<u8, Global>
impl From<Cow<'_, str>> for Box<str, Global>
impl<'a> From<&'a str> for Cow<'a, str>
impl From<String> for Box<str, Global>

Если: `From<T> for U` "auto implies" => `Into<U> for T`

fn main(){
// impl From<&str> for String
   let string:String = From::from("foo");

// impl<'a> From<&'a str> for Cow<'a, str> "auto implies" => `Into> for &str`
   use std::borrow::Cow;
   let cow:Cow = From::from("foo");
   let cow:Cow = "foo".into();

// impl From<&str> for Arc "auto implies" => `Into> for &str`
    use std::sync::Arc;
    let shared:Arc = Arc::from("eggplant");
    let shared:Arc = "eggplant".into();

// impl From<&str> for Rc "auto implies" => `Into> for &str`
    use std::rc::Rc;
    let shared:Rc = Rc::from("statue");
    let shared:Rc = "statue".into();

// impl From<&mut str> for String
    let mut buf:String = String::from("hello");
    let s:&mut str = buf.get_mut(0..).unwrap();
      // let s:&mut str = buf.as_mut();
      // let s:&mut str = buf.borrow_mut();
    let buff:String = String::from(s);

//impl From<&str> for Vec "auto implies" => `Into> for &str`
    let bytes:Vec = Vec::from("foo");
    let bytes:Vec = "foo".into();
}


fn main(){
// Получаем значения ошибок
    let io_err: io::Error = io::Error::last_os_error();
    let parse_err: num::ParseIntError = "not a number".parse::().unwrap_err();

// Собственно, конвертация
    let err1: Box = From::from(io_err);
    let err2: Box = From::from(parse_err);
}
impl BorrowMut<str> for String
impl Borrow<str> for String
impl AsMut<str> for String
impl AsMut<str> for str
impl AsRef<str> for String
impl AsRef<OsStr> for str
impl AsRef<Path> for str
impl AsRef<[u8]> for str

fn main(){
// impl BorrowMut for String
    use std::borrow::BorrowMut;
    let mut buf:String = String::from("hello");
    let mut s:&mut str = buf.borrow_mut();
    
// impl Borrow for String
     use std::borrow::Borrow;
     let buf:String = String::from("hello");
     let s:&str = buf.borrow();

// impl AsMut for String
    let mut buf:String = String::from("hello");
    let mut s:&mut str = buf.as_mut();

// impl AsMut for str
    let s2:&mut str = s.as_mut();

// impl AsRef<[u8]> for str  
    let arr:&[u8] = "hello".as_ref();

// impl AsRef for str
    let p:&std::path::Path = ".".as_ref();

// impl AsRef for String
    let buf:String = String::from("hello");
    let s:&str = buf.as_ref();

// impl AsRef for str
    let os:&std::ffi::OsStr = "hello".as_ref();
}

Вернуть &str

Если lifetime входа нет, то lifetime выхода должен быть ограничен lifetim'om входа.


fn main(){ 
  let r = get_str();
  println!(""{}"",r);
}
fn get_str<'a>() -> &'a str{
     "hello"
}

// Или вернуть 'static
fn foo()->&'static str{"hello"}

Это работает потому что мы возвращаем ссылку из области времени жизни main fn first_word<'a>(s: &'a str) -> &'a str


fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}
fn main() {
    let sentence = String::from("Hello World");
    let res:&str = first_word(&sentence);
    println!("{}",res);
}

Пример сравнение &str и i32


use std::str::Chars;
fn foo(s:&str,n:i32) -> bool{
    let mut chars:Chars<'_> = s.chars();
    let (mut x, mut y,mut count_cmp) = (10,1,0);
    while let Some(c)=chars.next_back(){
            if y <= n {
                if (n%x)/y != c.to_digit(10).unwrap() as i32{
                    return false;
                }
                x=x*10;
                y=y*10;
                count_cmp+=1;
            }  
    }
    count_cmp == s.len() && y > n
}
fn main() {
 assert_eq!(true,foo("1",1));
 assert_eq!(false,foo("2",1));
 assert_eq!(true,foo("123",123));
} 
  • std::concat! - макрос объединяет литералы в &str
  • std::stringify! - макрос создает &str из всех токенов

macro.concat

macro.stringify


fn main(){
    let s:&str = concat!("test", 10, 'b', true,44.88);
    assert_eq!(s, "test10btrue44.88");

    let one_plus_one = stringify!(1 + 1);
    assert_eq!(one_plus_one, "1 + 1");
}
  • is_empty() - Проверка длины
  • len() - Возвращает длину в байтах
  • as_ptr() - Преобразует в *const u8 raw poiner
  • as_mut_ptr() - Преобразует в *mut u8 raw poiner
  • repeat(n) - Повторяет строку n раз, возвращает String

  • parse::<T>() - Преобразует строку в тип Result<T,_>

  • split() - разбивает строку по Pattern и выдает по ним итератор
  • rsplit() - разбивает строку по Pattern и выдает по ним итератор в обратном порядке.
  • split_ascii_whitespace() - Разбивает фрагмент строки по пробелу ASCII
  • split_at() - Разделяет строку на две по индексу.
  • split_at_mut() - Разделите один изменяемый фрагмент строки на два по индексу.
  • split_inclusive() - как split но оставляет разделитель в строке
  • split_once() - возвращает префикс и суффикс относительно разделителя
  • rsplit_once() - как split_once но по последнему разделителю в строке
  • split_terminator() - как split но без включения завершаюшей строки
  • rsplit_terminator() - как split_terminator но с конца
  • split_whitespace() - Разбивает фрагмент строки по пробелам.
  • splitn() - как split но с ограничением
  • rsplitn() - как splitn но с конца

  • slice_unchecked(begin, end)->&str - Создает строковый срез из другого среза строки

  • contains(Pattern) -> bool - проверка вхождение подстроки
  • starts_with(Pattern) -> bool - проверка начала строки
  • ends_with(Pattern) -> bool - проверка конца строки
  • find(Pattern) -> Option<usize> - Возвращает индекс байта первого символа строки
  • rfind(Pattern) -> Option<usize> - Возвращает индекс байта последнего совпадения

  • matches(Pattern) -> Iterator<Item = &str> - фильтрует совпавшее
  • rmatches(Pattern) -> Iterator<Item = &str> - как matches() только сбор с конца строки
  • match_indices(P) -> Iterator<Item = (usize, &str)> - как matches() только возвращает номером байта совпадения
  • rmatch_indices(P) -> Iterator<Item = (usize, &str)> - как match_indices() только сбор с конца строки

  • is_char_boundary() - проверка начала utf-8

  • as_bytes() - преобразование в байтовый срез
  • as_bytes_mut(&mut self) -> &mut [u8] - преобразование &str/String в изменяемый байтовый срез
  • bytes() - Итератор по байтам строкового среза.
  • into_boxed_bytes() - Преобразует Box<str> в Box<[u8]> без копирования или выделения

  • get(0..) - возвращает Option<&str> из &str/String
  • get_mut(..8) - возвращает Option<&mut str> из &mut str/mut String
  • get_unchecked() - возвращают срез не проверенный через Option
  • get_unchecked_mut() - возвращают не проверенный через Option

  • chars() - Возвращает итератор по символам строкового среза
  • char_indices() - Возвращает итератор над символами строкового среза и их позиций в виде кортежа.

  • trim() - удаляет пробелы с концов
  • trim_start() - Возвращает фрагмент строки с удаленным начальным пробелом.
  • trim_start_matches() - Возвращает фрагмент строки со всеми префиксами, которые неоднократно удаляются из шаблона.
  • trim_matches() - удаляет с концов строки заданные char символы
  • trim_end() - Возвращает фрагмент строки с удаленными конечными пробелами.
  • trim_end_matches() - Возвращает фрагмент строки со всеми неоднократно удаленными суффиксами, которые соответствуют шаблону.
  • strip_suffix() - Возвращает строку с удаленным суффиксом
  • strip_prefix() - Возвращает строку с удаленным префиксом

  • to_lowercase() -> String - уменьшает регистр
  • to_uppercase() -> String - увеличивает регистр
  • make_ascii_lowercase() - Преобразует эту строку в эквивалентный ASCII строчный эквивалент на месте.
  • make_ascii_uppercase() - Преобразует эту строку в ее эквивалент ASCII в верхнем регистре на месте.
  • to_ascii_uppercase(), дает копию этой строки в верхнем регистре ASCII.
  • to_ascii_lowercase() - нижний регистр ASCII

  • replace() - замена строки ,возвращает String
  • replacen() - замена не более N раз, возвращает String

  • into_string() - Преобразует Box<str> в String без копирования или выделения.

  • encode_utf16() - Возвращает итератор для u16 строки, закодированной как UTF-16.
  • eq_ignore_ascii_case() - Проверяет, что две строки являются ASCII без учета регистра.
  • escape_debug() - Вернуть итератор, который экранирует каждый символ в sс char::escape_debug
  • escape_default() - Вернуть итератор, который экранирует каждый символ в sс char::escape_default
  • escape_unicode() - Вернуть итератор, который экранирует каждый символ в sс char::escape_unicode

  • is_ascii() - Проверяет, все ли символы в этой строке находятся в диапазоне ASCII

concat! макрос


fn main(){
    let c = r"foo\bar";
    let d = concat!(r"foo\", r"bar");
    assert_eq!(c, d);
// ---------------------------------------------
// Из Vec можно склеить строку
    let bits = vec!["veni", "vidi", "vici"];
    assert_eq!(bits.concat(), "venividivici");
    assert_eq!(bits.join(", "), "veni, vidi, vici");
}
  • len() - количество байт
  • is_empty() - проверка на пустоту

fn main(){
// Метод .len() типа String или &str возвращает длину строки, измеряемую в байтах, а не в символах:
    assert_eq!("ಠ_ಠ".len(), 7);

// Метод chars().count() считыет количество символов
    assert_eq!("ಠ_ಠ".chars().count(), 3);
// ----------------------------------------------------------------------------
    let str ="Тестовая строка для примеров";
    if !str.is_empty() {
        println!("{}",str.len());// 53 байт (для кириллицы 2 байта символ, для латиницы 1 байт)
    }
}
  • as_ptr() - Преобразует срез строки в исходный указатель указателя на некоторые байты
  • as_mut_ptr() - Преобразует изменяемый фрагмент строки в необработанный указатель.

fn main(){
    let s = "Hello";
    let ptr:*const u8 = s.as_ptr();
}

repeat(N) - повтор строки, возвращает String


fn main(){
    println!("{}","abc".repeat(4));// abcabcabcabc
}

parse::<T>() - Преобразует строку в тип T. Возвращает Result

неявно использует реализацию FromStr


fn main(){
    //let four: u32 = "4".parse().unwrap();
    let four = "4".parse::();
    if let Ok(n) =  "4".parse::(){
        println!("{}",n);// 17
    }
}


fn main(){
    macro_rules! parse_input {
        ($x:expr, $t:ident) => ($x.trim().parse::<$t>().unwrap())
    }

    let mut input_line = String::new();
    io::stdin().read_line(&mut input_line).unwrap();
    let n = parse_input!(input_line, usize);

}


fn main(){
    use std::str::FromStr;
    let s = "5";
    let x = i32::from_str(s).unwrap();
}

Splitting to iterator

Splitting to iterator

  • split(Pattern) -> Iterato - разбивает строку по Pattern и выдает по ним итератор

  • rsplit (Pattern) -> Iterator

  • splitn (usize, Pattern) -> Iterator - как split но с ограничением

  • rsplitn (usize, Pattern) -> Iterato - как rsplit но с ограничением

  • split_at(usize) -> (&str, &str) - Разделяет строку на две по индексу.

  • split_at_mut() - Делит строку на два фрагмента по номеру байта

  • split_whitespace() -> Iterator - разделитель [по пробелу, \n , \t] Возвращенный итератор вернет строковые срезы, которые являются суб-срезами исходного среза строки, разделенные любым количеством пробелов.

  • lines() - Итератор по разделенным строкам \n

  • split_terminator(Pattern) -> Iterator - как split но без включения завершающей строки

  • rsplit_terminator(Pattern) -> Iterator

  • split_ascii_whitespace() -> Iterator - Разбивает фрагмент строки по пробелу ASCII



fn main(){
    let v: Vec<&str> = "00:32:14.059 --> 00:32:16.687".split("-->").collect();
    assert_eq!("00:32:14.059 ",v[0]);
    assert_eq!(" 00:32:16.687",v[1]);
    println!("{:?}", v);
// -----------------------------------------------------
    let s = String::from("Per Martin-Löf");
    let s =  "Per_Martin-Löf" ;
    let (first, last) = s.split_at_mut(3);
    first.make_ascii_uppercase();
    println!("{}",first);//Per
    println!("{}",last);//_Martin-Löf
// -----------------------------------------------------
    let mut s = String::from("Per_Martin-Löf");
    if let Some(index) = s.find('_'){
         assert_eq!(3,index);
         let (first, last) = s.split_at_mut(index);
         println!("{}",first);//Per
         println!("{}",last);//_Martin-Löf
    }
// ------------------------------------------------------
    let mut iter = "МИР\tТРУД МАЙ".split_whitespace();
    while let  Some( mut s) = iter.next(){
        // s:&str
        print!("{}_", s);// МИР_ТРУД_МАЙ_
    } 
// -----------------------------------------------------------------------
    let text = "foo\r\nbar\n\nbaz\n";
    let mut lines = text.lines();
    while let  Some( mut s) = lines.next(){
        // s:&str
        print!("{}_", s);// foo_bar__baz_
    }
}

unsafe fn slice_unchecked(&self, begin: usize, end: usize) -> &str - Создает строковый срез из другого среза строки, минуя проверки безопасности.

Для безопасной альтернативы см. Str и Index. от начала до конца, включая начало, но исключая конец.


fn main(){
    let s = "Löwe 老虎 Léopard";
    unsafe {
        println!("{}",s.slice_unchecked(0, s.len()));// Löwe 老虎 Léopard
    }
}
  • contains(Pattern) -> bool - проверка вхождение подстроки
  • starts_with(Pattern) -> bool - проверка начала строки
  • ends_with(Pattern) -> bool - проверка конца строки

fn main(){
    let bananas = "банан";
    if bananas.contains("нан"){println!("yes");}else{println!("no");}
    if bananas.starts_with("бан"){println!("yes");}else{println!("no");}
    if bananas.ends_with("анан"){println!("yes");}else{println!("no");}
}

find(Pattern) -> Option<usize> - Возвращает индекс байта первого символа этого строкового среза, который соответствует шаблону

Возвращает индекс байта первого символа этого строкового среза, который соответствует шаблону. Возвращает None, если шаблон не совпадает. std::str::pattern::Pattern может быть символом &str, char или лямбда, который определяет, соответствует ли символ.

rfind(Pattern) -> Option<usize> - Возвращает индекс байта последнего совпадения

method.find

trait.Pattern


fn main(){
    let s = "Привет Мир!";
    let s = "Hello World!";
    // char ------------------------------
    if let Some(n) =  s.find('е'){
        println!("{}",n);// 17
    }
    // &str -------------------------------
    if let Some(n) =  s.find("Мир"){
        println!("{}",n);// 13
    }
    // &[char] ---------------------------
    let s = "Hello World!";
    let x: &[_] = &['W', 'd'];// первый совпавший символ
    if let Some(n) = s.find(x){
        println!("{}",n);// 6
    }
}


fn main(){
    // char method --------------------
    assert_eq!("Hello World!".find(char::is_whitespace), Some(5));
    // FnMut(char) -> bool ----------
    {
        // возвращает байт последнего символа совпавшей строки
        let mut buff:String = String::from("");
        if let Some(n) =  s.find( move |_char:char |{
            if _char.is_whitespace(){ println!("is_whitespace");}
            if _char.is_lowercase(){ println!("is_lowercase");}
            if _char < 'a' { println!("< 'a' ");}
            buff.push(_char);
            println!("buff={} ",buff);
           if buff.contains("Hello"){ true }
           else{ false }
        }){
            println!("{}",n);// 4-й байт
        }
    }
}


fn main(){
    // rfind() -  Возвращает индекс байта последнего совпадения
    let s = "Hello World!";
    let x: &[_] = &['W', 'd'];
    if let Some(n) =  s.rfind(x){
        println!("{}",n);// 10
    }
}
  • matches(Pattern) -> Iterator<Item = &str> - фильтрует совпавшее
  • rmatches(Pattern) -> Iterator<Item = &str> - как matches() только сбор с конца строки
  • match_indices(Pattern) -> Iterator<Item = (usize, &str)> - как matches() только возвращает кортеж с номером байта совпадения
  • rmatch_indices(Pattern) -> Iterator<Item = (usize, &str)> - как match_indices() только сбор с конца строки разделитель - std::str::pattern::Pattern

Итератор по непересекающимся совпадениям шаблона в пределах данного среза строки.


fn main(){
    // Если нужно вхождение:
    let v: Vec<&str> = "abcXXXabcYYYabc".matches("abc").collect();
    let mut string:String = String::from("");
    for i in &v {
        print!("{} ",i);// abc abc abc
        string+=*i;
    }
    let v: Vec<&str> = "1abc2abc3".matches(char::is_numeric).collect();
    assert_eq!(v, ["1", "2", "3"]);

    // Если нужно точное совпадение слова:
    let mut text = "If you to your office late so often, you are in for a great trouble";
    let is_word:bool = text.split_whitespace().any(|w|w=="office");

    // Если нужно точное совпадение подстроки:
    let re = &format!(r"\b({})\b","office");
    regex::Regex::new(re).unwrap().is_match(text)
}


fn main(){
    let v: Vec<&str> = "abcXXXabcYYYabc".matches("abc").collect();
    let mut string:String = String::from("");
    for i in &v {
        print!("{} ",i);// abc abc abc
        string+=*i;
    }
    println!("string={}",string);// abcabcabc

    let v: Vec<&str> = "1abc2abc3".matches(char::is_numeric).collect();
    for i in &v {
        print!("{} ",i);// 1 2 3
    }
 
    //match_indices
    let v: Vec<_> = "1одинодин2".match_indices("один").collect();
    println!("{:?}",v);// [(1, "один"), (9, "один")]

}

is_char_boundary() - проверка начала utf-8

Проверяет, что индексный байт лежит в начале и/или конце последовательности кодовых точек UTF-8.


fn main(){
    println!("{}",str.is_char_boundary(1));// для кириллицы первый байт - true , второй - false
}
  • as_bytes() - преобразование в байтовый срез (Обратно str::from_utf8)

    сrate bstr: Строковые методы для байтовых строк

  • as_bytes_mut(&mut self) -> &mut [u8] - преобразование &str/String в изменяемый байтовый срез (Обратно str::from_utf8_mut)

  • bytes() - Итератор по байтам строкового среза.

  • into_boxed_bytes() - Преобразует Box<str> в Box<[u8]> без копирования или выделения


fn main(){
    let bytes:&[u8] = "bors".as_bytes();
    assert_eq!([98, 111, 114, 115],bytes);

    let bytes2 =b"bors";
    assert_eq!(bytes2, bytes);

    // Чтобы преобразовать байтовый фрагмент обратно в срез строки, используйте функцию str::from_utf8
    let bytes = std::str::from_utf8(&bytes);     // https://doc.rust-lang.org/beta/std/str/fn.from_utf8.html
    println!("bytes = {}",bytes.unwrap());// bors
}

fn main(){
    let b:&[u8] = b"some bytes";
    let b:Vec = vec![115, 111, 109, 101, 32, 98, 121, 116, 101, 115];
    let b:&[u8] = "some bytes".as_bytes();
    let b:Vec = String::from("some bytes").into_bytes();
    println!("{:?}",b);// [115, 111, 109, 101, 32, 98, 121, 116, 101, 115]

    println!("{}",if let Ok(result)=std::str::from_utf8(&b){result}else {""});
}


fn main(){
    let mut buf = String::from("🗻∈🌏");
    let s:&mut str = buf.as_mut();
    unsafe {
        let  bytes:&mut[u8] = s.as_bytes_mut();
        bytes[0] = 0xF0;
        bytes[1] = 0x9F;
        bytes[2] = 0x8D;
        bytes[3] = 0x94;
    }
    Обратное преобразование:
    unsafe {
        let mut mut_s:&mut str = std::str::from_utf8_mut(bytes).unwrap();
        println!("mut_s = {}",mut_s);
    }
    println!("s = {}",s);// 🍔∈🌏
}


fn main(){
    let mut bytes = "bors".bytes();
    while let  Some( mut s) = bytes.next(){
        print!("{} ", s);// 98 111 114 115
    }
    // ------------------------------------------------------
    let s = "this is a string";
    let boxed_str:Box = s.to_owned().into_boxed_str();
    let boxed_bytes:Box<[u8]> = boxed_str.into_boxed_bytes();
    println!("{:?} \n{:?}",boxed_bytes, s.as_bytes());
}
  • get(0..) - возвращает Option<&str> из &str/String

  • get_mut(..8) - возвращает Option<&mut str> из &mut str/mut String

  • get_unchecked() - возвращают срез не проверенный через Option

  • get_unchecked_mut() - возвращают не проверенный через Option


fn main(){
    let v: String = String::from("Привет");
    let v:&str = "Привет";
    let opt:Option<&str> = v.get(0..);
    if let Some(i) = opt{  println!("{}",i);}
    //let v: Vec = From::from("hello");
    //for i in v.iter() {  print!("{} ",*i);}
}


fn main(){
    let mut buf:String = String::from("hello");
    let s:&mut str = buf.as_mut();
    let s:Option<&mut str> = s.get_mut(0..);
    let s = s.map(|s| {
        print!("{},",&*s);
        s.make_ascii_uppercase();// для латиницы
        &*s
    });
    if let Some(i) = s{
        println!("{}",i);// HELLO
    }
}

  • Начальный индекс должен быть до конца индекса;
  • Индексы должны находиться в пределах исходного фрагмента;
  • Индексы должны лежать на границах последовательности UTF-8.

В противном случае возвращаемый фрагмент строки может ссылаться на недопустимую память или нарушать инварианты, передаваемые типом str.


fn main(){
    let v:String = String::from("Привет");
    let v:&str = "Привет";
    unsafe {
        let opt:Option<&str> = v.get_unchecked(0..55);

        if let Some(i) = opt{
            println!("{}",i);
        }
    }
}
  • chars() - Возвращает итератор по символам строкового среза

  • char_indices() - Возвращает итератор над символами строкового среза и их позиций в виде кортежа.


fn main(){
    let word = "привет";
    let mut chars = word.chars();

    let mut string = String::from("");
    let mut v: Vec = vec![];
    while let  Some(mut s) = chars.next(){
            print!("{}",s);
            s.make_ascii_uppercase();// по символу тоже возможно
            v.push(s);
            string.push(s);
    }
}


fn main(){
    let s:String = "Hello, world!".chars()
        .map(|x| match x { 
            '!' => '?', 
            'A'...'Z' => 'X', 
            'a'...'z' => 'x',
            _ => x}
        ).collect();
    println!("{}", s);// Xxxxx, xxxxx?
}


fn main(){
    let word = "привет";
    let mut char_indices = word.char_indices();

    let mut string = String::from("");
    let mut v: Vec = vec![];
    while let  Some((indx,mut s)) = char_indices.next(){
        println!("indx={} s={}",indx,s);
        //indx=0 s=п
        //indx=2 s=р
        //indx=4 s=и
        //indx=6 s=в
        //indx=8 s=е
        //indx=10 s=т
        s.make_ascii_uppercase();// по символу тоже возможно
        v.push(s);
        string.push(s);
    }

    let mut v = word.chars().collect::>();
    println!(" Vec = {:?}",v);//Vec = ['п', 'р', 'и', 'в', 'е', 'т']
}


fn main(){
    let word = "привет";
    let mut chars = word.chars();
    // let count = chars.count();

       // for s in &mut chars {    print!("{}", s);//  привет }
     // перемотать назад итератор перед использованием еще раз
    let mut string = String::from("");
    let mut v: Vec = vec![];
    while let  Some(mut s) = chars.next(){
            print!("{}",s);
            s.make_ascii_uppercase();// по символу тоже возможно
            v.push(s);
            string.push(s);
    }
    string.make_ascii_uppercase();// тут всю строку
    println!("\nstring = {}",string);// привет
    println!(" Vec = {:?}",v); //Vec = ['п', 'р', 'и', 'в', 'е', 'т']

    let mut v = word.chars().collect::>();
    println!(" Vec = {:?}",v);//Vec = ['п', 'р', 'и', 'в', 'е', 'т']
}
  • trim() - удаляет пробелы с концов

  • trim_start() - Возвращает фрагмент строки с удаленным начальным пробелом.

  • trim_start_matches() - Возвращает фрагмент строки со всеми префиксами, которые неоднократно удаляются из шаблона.

  • trim_matches() - удаляет с концов строки заданные char символы

  • strip_suffix() - Возвращает строку с удаленным суффиксом

  • strip_prefix() - Возвращает строку с удаленным префиксом


fn main(){
    let s = " Hello\tworld\t";
    println!("|{}|",s.trim());// |Hello        world|

    println!("{}","11foo1bar11".trim_matches('1') );// foo1bar
}


fn main(){
    let mut bananas = "-Then 0le-ole quickly-".to_string();
    if bananas.starts_with("'"){
      bananas = bananas.strip_prefix("'").unwrap().to_string();
    }
    if bananas.ends_with("'"){
      bananas = bananas.strip_suffix("-").unwrap().to_string();
    }
    assert_eq!("Then 0le-ole quickly",bananas.as_str());
}


// Удаление пробелов из середины строки
fn filter_chars(s:&str){
   let mut is_last_space = false;
   let result: String = s.chars().filter(|c| { 
        if is_last_space && c.is_whitespace()  {
            return false;
        }else if !is_last_space && c.is_whitespace(){
            is_last_space = true;
            return true;
        }else if !is_last_space && !c.is_whitespace(){
            return true;
        }else if is_last_space && !c.is_whitespace(){
            is_last_space = false;
            return true;
        }
        true
    }).collect();
   println!("{}", result);
}
fn filter_split_whitespace(s:&str){
    let mut result:String = s.split_whitespace().fold(String::from(""), |mut state, x| {
        state.push_str(&x);
        state.push_str(" ");
        state
    });
   let result = result.trim();
   println!("{}", result);

}
fn filter_regex(s:&str){
    use regex::Regex;
    let re = Regex::new(r"[ ]{2,}").unwrap();
    let result = re.replace_all(s, " ");
    println!("{}", result); // => Hello World
}
fn filter_dedup_by(s:&str){
    let mut s: Vec = Vec::from_iter(s.chars());
    s.dedup_by(|a, b| a.is_whitespace() && b.is_whitespace());
    let result: String = String::from_iter(s);
    println!("{result}",);
}
fn main() {
    filter_regex("aaa   bbb   ccc");
    filter_split_whitespace("aaa   bbb   ccc");
    filter_chars("aaa   bbb   ccc");
    filter_dedup_by("aaa   bbb   ccc");
}
  • to_lowercase() -> String - уменьшает регистр
  • to_uppercase() -> String - увеличивает регистр
  • make_ascii_lowercase() - Преобразует эту строку в эквивалентный ASCII строчный эквивалент на месте.
  • make_ascii_uppercase() - Преобразует эту строку в ее эквивалент ASCII в верхнем регистре на месте.
  • to_ascii_uppercase(), дает копию этой строки в верхнем регистре ASCII.
  • to_ascii_lowercase() - нижний регистр ASCII

fn main(){
    let s = "ПРИВЕТ";
    println!("{}",s.to_lowercase());// привет
    let s = "привет";
    println!("{}",s.to_uppercase());// ПРИВЕТ
}


fn main(){
    let mut s = String::from("Grüße, Jürgen ❤");
    s.make_ascii_uppercase();
    assert_eq!("GRüßE, JüRGEN ❤", s);
}


fn main(){
    let s = "Grüße, Jürgen ❤"; 
    assert_eq!("grüße, jürgen ❤", s.to_ascii_lowercase());
    assert_eq!("GRüßE, JüRGEN ❤", s.to_ascii_uppercase());
}
  • replace() - замена строки, возвращает String
  • replacen() - замена не более N раз, возвращает String

fn main(){
    // replace создает новый String и копирует в него данные из этого фрагмента строки.
    let s:&str = "this is old";
    let new_str:&str = &s.replace("old", "new");
    println!("{}",new_str);// this is new
    //let s = "foo foo 123 foo";
    //assert_eq!("new new 123 foo", s.replacen("foo", "new", 2));
}


fn main(){
    use regex::Regex;
    let re = Regex::new(r"[^\w,. ]").unwrap();
    let result = re.replace_all("Hello World!?", " ");
    println!("{}", result); // => Hello World
}

into_string() - Преобразует Box<str> в String без копирования или выделения памяти


fn main(){
    let string:String = String::from("birthday gift");
    let boxed_str:Box = string.clone().into_boxed_str();
    assert_eq!(boxed_str.into_string(), string);
}