Для чего нужна данная статья? :
Научиться заменять циклы слайсом используя итераторы, работать с любыми непрерывными данными, безопасно манипулировать памятью и использовать преимущества статической типизации.
Зачем Вам это уметь? :
1. Ссылочные слайсы (&[T] и &mut [T])
Слайсы в Rust часто используются как ссылки на массив или вектор. Они неизменяемые или изменяемые:
Неизменяемый слайс (&[T]):
let array = [1, 2, 3, 4];
let slice: &[i32] = &array[1..3];
println!("{:?}", slice); // [2, 3]
Изменяемый слайс (&mut [T]):
let mut array = [1, 2, 3, 4];
let slice: &mut [i32] = &mut array[1..3];
slice[0] = 5;
println!("{:?}", array); // [1, 5, 3, 4]
Вы можете перебирать элементы слайса с помощью итераторов:
fn main() {
let array = [10, 20, 30, 40, 50];
let slice: &[i32] = &array[1..4];
for (i, value) in slice.iter().enumerate() {
println!("Элемент {}: {}", i, value); // Элемент 0: 20, Элемент 1: 30, Элемент 2: 40
}
}
С помощью итератора можно модифицировать элементы изменяемого слайса:
fn main() {
let mut array = [1, 2, 3, 4, 5];
let slice: &mut [i32] = &mut array[1..4];
for value in slice.iter_mut() {
*value *= 2; // Удваиваем значение каждого элемента
}
println!("{:?}", array); // [1, 4, 6, 8, 5]
}
2. Слайсы строк (&str)
Слайс строки (&str) — это специальный вид неизменяемого слайса, который указывает на последовательность символов UTF-8:
let text = "Hello, Rust!";
let slice: &str = &text[7..11];
println!("{}", slice); // Rust
Срезы строк работают с байтами, поэтому использование некорректных границ может вызвать ошибку. Например:
fn main() {
let text = "Привет"; // "Привет" в UTF-8 занимает больше байтов
// Ошибка выполнения: границы среза не совпадают с границами символов UTF-8
let slice = &text[0..3];
println!("{}", slice);
}
Для безопасной работы используйте методы, учитывающие символы:
fn main() {
let text = "Привет";
let slice = &text[..4]; // Берем первые 4 символа (включая UTF-8)
println!("{}", slice); // Прив
}
Можно использовать метод .split() для получения срезов строк:
fn main() {
let text = "Hello Rust World";
for word in text.split_whitespace() {
println!("{}", word);
}
// Вывод:
// Hello
// Rust
// World
}
С векторами удобно работать через .as_slice() или .as_mut_slice():
fn main() {
let mut vec = vec![10, 20, 30, 40];
let slice = vec.as_slice();
println!("{:?}", slice); // [10, 20, 30, 40]
let mut_slice = vec.as_mut_slice();
mut_slice[0] = 50; // Изменяем первый элемент
println!("{:?}", vec); // [50, 20, 30, 40]
}
3. Динамические массивы (Vec<T>)
Хотя Vec<T> не является слайсом, он предоставляет метод .as_slice(), чтобы получить ссылочный слайс:
let vec = vec![10, 20, 30, 40];
let slice: &[i32] = &vec[1..3];
println!("{:?}", slice); // [20, 30]
4. Слайсы с произвольной длиной (Box<[T]>)
Вы можете использовать Box<[T]> для создания слайсов, выделенных в куче. Это полезно для работы с динамическими массивами:
let boxed_slice: Box<[i32]> = vec![1, 2, 3, 4].into_boxed_slice();
println!("{:?}", boxed_slice);
Box<[T]> позволяет передавать владение массивом с произвольной длиной:
fn main() {
let boxed_slice: Box<[i32]> = vec![1, 2, 3, 4].into_boxed_slice();
println!("{:?}", boxed_slice); // [1, 2, 3, 4]
}
5. Создание слайсов через std::slice
Модуль std::slice предоставляет функции для создания слайсов вручную:
Создание слайса из сырого указателя:
use std::slice;
let data = [1, 2, 3, 4];
let ptr = data.as_ptr();
let slice: &[i32] = unsafe { slice::from_raw_parts(ptr, 4) };
println!("{:?}", slice); // [1, 2, 3, 4]
Из изменяемого указателя:
let mut data = [5, 6, 7, 8];
let ptr = data.as_mut_ptr();
let slice: &mut [i32] = unsafe { slice::from_raw_parts_mut(ptr, 4) };
slice[0] = 10;
println!("{:?}", data); // [10, 6, 7, 8]
Когда мы работаем с указателями, важно помнить об unsafe блоках:
use std::slice;
fn main() {
let data = [10, 20, 30, 40];
let ptr = data.as_ptr();
let slice = unsafe { slice::from_raw_parts(ptr, 4) };
println!("{:?}", slice); // [10, 20, 30, 40]
}
6. Слайсы через массивы фиксированной длины
Слайсы можно получить из массивов фиксированной длины:
let array: [i32; 4] = [1, 2, 3, 4];
let slice: &[i32] = &array[..];
println!("{:?}", slice); // [1, 2, 3, 4]
7. Динамические срезы в других структурах (например, Cow<[T]>)
Слайсы могут быть использованы в Cow (Copy-On-Write), который позволяет оптимизировать копирование данных:
use std::borrow::Cow;
let slice: &[i32] = &[1, 2, 3];
let cow: Cow<[i32]> = Cow::Borrowed(slice);
println!("{:?}", cow); // [1, 2, 3]
8. Типовые параметры и обобщённые слайсы
Слайсы могут использоваться в функциях с обобщёнными типами:
fn print_slice<T: std::fmt::Debug>(slice: &[T]) {
println!("{:?}", slice);
}
let array = [1, 2, 3, 4];
print_slice(&array[1..3]); // [2, 3]
Слайсы идеально подходят для функций, работающих с любыми типами:
fn sum_elements<T: std::ops::Add<Output = T> + Copy>(slice: &[T]) -> T {
slice.iter().copied().fold(T::default(), |acc, x| acc + x)
}
fn main() {
let array = [1, 2, 3, 4];
let result = sum_elements(&array);
println!("Сумма: {}", result); // Сумма: 10
}
9. Пользовательские реализации через Deref
Можно создать свои структуры, которые ведут себя как слайсы, реализовав трейт Deref:
use std::ops::Deref;
struct MySlice {
data: Vec<i32>,
}
impl Deref for MySlice {
type Target = [i32];
fn deref(&self) -> &Self::Target {
&self.data
}
}
let my_slice = MySlice { data: vec![1, 2, 3] };
println!("{:?}", &*my_slice); // [1, 2, 3]
Если вы создаете структуру данных, которая должна вести себя как слайс, используйте Deref:
use std::ops::Deref;
struct MySlice {
data: Vec<i32>,
}
impl Deref for MySlice {
type Target = [i32];
fn deref(&self) -> &Self::Target {
&self.data
}
}
fn main() {
let my_slice = MySlice { data: vec![1, 2, 3] };
println!("{:?}", &*my_slice); // [1, 2, 3]
}
10. Слайсы через Arc<[T]> или Rc<[T]>
Для управления памятью с использованием ссылочного счётчика можно применять Arc<[T]> или Rc<[T]>:
use std::sync::Arc;
let arc_slice: Arc<[i32]> = vec![1, 2, 3].into();
println!("{:?}", arc_slice); // [1, 2, 3]
Для многопоточных программ удобно использовать Arc<[T]>:
use std::sync::Arc;
fn main() {
let arc_slice: Arc<[i32]> = vec![1, 2, 3].into();
let arc_clone = arc_slice.clone();
println!("{:?}", arc_slice); // [1, 2, 3]
println!("{:?}", arc_clone); // [1, 2, 3]
}