std::str
: Работа со строками (UTF-8)
2. std::string
: Управление String
3. std::vec
: Работа с Vec
как отдельным модулем
4. std::iter
: Итераторы и их использование
Практические советы
Пример: Комбинирование модулей
Упражнение
Заключение
В этом разделе мы рассмотрим модули стандартной библиотеки Rust, связанные с обработкой данных: std::str
, std::string
, std::vec
и std::iter
. Эти модули предоставляют инструменты для работы со строками, динамическими массивами и итераторами — основными строительными блоками для манипуляции данными в Rust. Лекция подойдёт как новичкам, так и опытным разработчикам, желающим углубить знания. Мы разберём каждый модуль с примерами, нюансами и практическими советами.
std::str
: Работа со строками (UTF-8)Модуль std::str
предоставляет функции для работы с &str
— неизменяемыми строками в формате UTF-8. Это базовый тип для текстовых данных в Rust.
Основные возможности:
Пример: базовые операции со строками:
fn main() {
let text = "Hello, Rust!";
// Поиск подстроки
if text.contains("Rust") {
println!("Найдено: Rust");
}
// Разбиение строки
let words: Vec<&str> = text.split_whitespace().collect();
println!("Слова: {:?}", words); // ["Hello,", "Rust!"]
// Преобразование регистра
println!("В верхнем: {}", text.to_uppercase()); // HELLO, RUST!
}
Комментарии:
contains
проверяет наличие подстроки.split_whitespace
возвращает итератор по словам.to_uppercase
возвращает новый String
, так как &str
неизменяем.Заметка: Все строки в Rust — это валидный UTF-8. Для работы с сырыми байтами используйте &[u8]
.
std::string
: Управление String
Модуль std::string
определяет тип String
— динамическую, мутабельную строку в формате UTF-8, которая владеет своими данными.
Основные методы:
push
, push_str
: Добавление данных.pop
, truncate
: Удаление данных.from
: Создание из других типов.Пример: манипуляции с String
:
fn main() {
let mut s = String::from("Hello");
// Добавление
s.push('!');
s.push_str(" Rust");
println!("После добавления: {}", s); // Hello! Rust
// Удаление
s.pop();
println!("После pop: {}", s); // Hello! Rus
// Очистка
s.truncate(5);
println!("После truncate: {}", s); // Hello
}
Комментарии:
push
добавляет один символ, push_str
— строку.pop
возвращает Option
, удаляя последний символ.truncate
обрезает строку до указанной длины (в байтах, но с учётом UTF-8).Предупреждение: Методы вроде truncate
паникуют, если обрезка нарушает границы UTF-8 символов.
std::vec
: Работа с Vec
как отдельным модулемМодуль std::vec
определяет тип Vec<T>
— динамический массив, который мы уже встречали в std::collections
. Здесь он рассматривается как самостоятельный модуль с акцентом на его методы и свойства, включая управление памятью.
Основные методы:
push
, pop
: Добавление и удаление в конце.insert
, remove
: Вставка и удаление по индексу.capacity
, reserve
: Управление памятью.Пример: работа с Vec
:
fn main() {
let mut v = Vec::with_capacity(5); // Начальная ёмкость
// Добавление
v.push(1);
v.push(2);
v.insert(1, 3); // Вставка на позицию 1
println!("Вектор: {:?}", v); // [1, 3, 2]
// Удаление
let removed = v.remove(0);
println!("Удалено: {}, Вектор: {:?}", removed, v); // 1, [3, 2]
// Ёмкость
println!("Длина: {}, Ёмкость: {}", v.len(), v.capacity());
}
Комментарии:
with_capacity
минимизирует перевыделение памяти.insert
и remove
имеют сложность O(n) из-за сдвига элементов.capacity
показывает выделенную память, а len
— количество элементов.capacity
: Метод, возвращающий текущую ёмкость вектора — количество элементов, которое Vec
может вместить без перевыделения памяти.
fn capacity(&self) -> usize
.fn main() {
let mut v = Vec::new();
println!("Ёмкость: {}", v.capacity()); // 0
v.push(1);
println!("Ёмкость после push: {}", v.capacity()); // Обычно 4 (зависит от реализации)
v.push(2);
println!("Длина: {}, Ёмкость: {}", v.len(), v.capacity()); // 2, 4
}
Комментарии:
capacity
всегда больше или равно len
.Vec
перевыделяет память, обычно удваивая её.Vec::new()
равна 0.reserve
: Метод, который резервирует дополнительную память для вектора, чтобы избежать перевыделения при последующих вставках.
fn reserve(&mut self, additional: usize)
.fn main() {
let mut v = Vec::with_capacity(2); // Начальная ёмкость 2
println!("Начальная ёмкость: {}", v.capacity()); // 2
v.push(1);
v.push(2);
println!("Длина: {}, Ёмкость: {}", v.len(), v.capacity()); // 2, 2
v.reserve(5); // Резервируем место для 5 дополнительных элементов
println!("Ёмкость после reserve: {}", v.capacity()); // >= 7 (2 + 5)
v.push(3); // Без перевыделения
println!("Длина: {}, Ёмкость: {}", v.len(), v.capacity()); // 3, >=7
}
Комментарии:
reserve
принимает количество дополнительных элементов сверх текущей длины.reserve
ничего не делает.Пример: управление памятью в цикле:
fn main() {
let mut v = Vec::new();
let iterations = 1000;
// Без резервирования
let mut v_no_reserve = Vec::new();
for i in 0..iterations {
v_no_reserve.push(i);
}
println!("Без reserve - Длина: {}, Ёмкость: {}",
v_no_reserve.len(), v_no_reserve.capacity()); // 1000, >=1000
// С резервированием
let mut v_with_reserve = Vec::new();
v_with_reserve.reserve(iterations); // Заранее выделяем память
for i in 0..iterations {
v_with_reserve.push(i);
}
println!("С reserve - Длина: {}, Ёмкость: {}",
v_with_reserve.len(), v_with_reserve.capacity()); // 1000, >=1000
}
Комментарии:
reserve
вектор перевыделяет память несколько раз (O(log n) перевыделений).reserve
память выделяется один раз, что быстрее для больших данных.Заметка: Используйте reserve
, если заранее знаете, сколько элементов будет добавлено, чтобы избежать лишних перевыделений.
Предупреждение: reserve
не уменьшает ёмкость — для этого используйте shrink_to_fit
.
std::iter
: Итераторы и их использованиеМодуль std::iter
предоставляет инструменты для работы с итераторами — мощным механизмом для обработки последовательностей данных. Итераторы в Rust ленивы, то есть операции выполняются только при их потреблении (например, с помощью collect
или for
). Этот раздел подробно разберёт ключевые трейты и методы.
Основные черты:
Iterator
, IntoIterator
.map
, filter
, collect
.Пример: использование итераторов:
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// Фильтрация и преобразование
let even_squares: Vec<i32> = numbers.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * x)
.collect();
println!("Чётные квадраты: {:?}", even_squares); // [4, 16]
// Сумма
let sum: i32 = numbers.iter().sum();
println!("Сумма: {}", sum); // 15
}
Комментарии:
iter
возвращает итератор по ссылкам (&T
).filter
и map
ленивы, пока не вызван collect
.sum
потребляет итератор и требует типа с трейтом Sum
.Заметка: Используйте into_iter
для владения элементами и iter_mut
для мутабельных ссылок.
Iterator
: Основной трейт для итераторов, определяющий поведение последовательности. Любой тип, реализующий Iterator
, должен предоставить метод next
, который возвращает следующий элемент или None
, если элементы закончились.
fn next(&mut self) -> Option
.struct Counter {
count: u32,
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option {
if self.count < 5 {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
fn main() {
let counter = Counter { count: 0 };
for num in counter {
println!("{}", num); // 1, 2, 3, 4, 5
}
}
Комментарии: Здесь Counter
реализует Iterator
, возвращая числа от 1 до 5.
IntoIterator
: Трейт, который позволяет типу быть преобразованным в итератор. Он используется в конструкциях вроде for
, где коллекция автоматически преобразуется в итератор.
fn into_iter(self) -> Self::IntoIter
.fn main() {
let v = vec![1, 2, 3];
let iter = v.into_iter(); // Vec реализует IntoIterator
for num in iter {
println!("{}", num); // 1, 2, 3
}
}
Комментарии: into_iter
забирает владение Vec
, в отличие от iter
, который даёт ссылки.
map
: Метод итератора, который применяет функцию к каждому элементу, создавая новый итератор с преобразованными значениями.
fn map(self, f: F) -> Map where F: FnMut(Self::Item) -> B
.fn main() {
let numbers = vec![1, 2, 3];
let squares: Vec<i32> = numbers.iter().map(|&x| x * x).collect();
println!("{:?}", squares); // [1, 4, 9]
}
Комментарии: map
ленивый — результат вычисляется только при вызове collect
.
filter
: Метод итератора, который отфильтровывает элементы, оставляя только те, что удовлетворяют предикату.
fn filter<P>(self, predicate: P) -> Filter where P: FnMut(&Self::Item) -> bool
.fn main() {
let numbers = vec![1, 2, 3, 4];
let evens: Vec<i32> = numbers.iter().filter(|&&x| x % 2 == 0).map(|&x| x).collect();
println!("{:?}", evens); // [2, 4]
}
Комментарии: filter
принимает замыкание, возвращающее bool
.
collect
: Метод итератора, который собирает элементы в коллекцию (например, Vec
, String
).
fn collect<B>(self) -> B where B: FromIterator<Self::Item>
.fn main() {
let chars = vec!['a', 'b', 'c'];
let s: String = chars.into_iter().collect();
println!("{}", s); // "abc"
}
Комментарии: collect
требует, чтобы целевой тип реализовал FromIterator
.
Пример: объединение трейтов и методов:
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// Фильтрация и преобразование
let even_squares: Vec<i32> = numbers.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * x)
.collect();
println!("Чётные квадраты: {:?}", even_squares); // [4, 16]
}
Комментарии: Здесь Vec
реализует IntoIterator
, а iter
даёт Iterator
, который обрабатывается методами.
std::str
: Используйте trim
для очистки строк от пробелов перед обработкой.std::string
: Преобразуйте &str
в String
только при необходимости, чтобы избежать лишних выделений.std::vec
: Задавайте начальную ёмкость с with_capacity
для больших данных.std::iter
: Комбинируйте методы итераторов для выразительного и эффективного кода.Программа для обработки текста:
fn main() {
let text = "apple banana apple cherry";
let mut words = Vec::new();
// Разбиение строки и сбор в Vec
text.split_whitespace().for_each(|w| words.push(String::from(w)));
println!("Слова: {:?}", words);
// Подсчёт уникальных слов
let unique: Vec<String> = words.into_iter()
.filter(|w| w.len() > 5)
.collect();
println!("Уникальные (>5): {:?}", unique);
}
Этот пример использует str
для разбиения, String
для хранения, Vec
для коллекции и iter
для фильтрации.
Напишите программу, которая:
std::env::args
).std::str
.Vec<String>
с использованием std::string
.std::iter
.Подсказка: Используйте collect
для создания вектора.
Модули std::str
, std::string
, std::vec
и std::iter
— это основа для обработки данных в Rust. Они обеспечивают гибкость, производительность и безопасность при работе со строками, массивами и последовательностями. Освоив их, вы сможете эффективно манипулировать данными в своих программах. Далее мы рассмотрим работу с ошибками.
Выполните упражнение или двигайтесь дальше!