- Основы языка
- Переменные и типы данных
- Управление потоком выполнения
- Функции и замыкания
- Владение памятью: move, borrow, lifetime
- Структуры данных
- Перечисления и паттерн-матчинг
- Обработка ошибок: Option, Result, panic
- Трейты
- Generics
- Коллекции и итераторы
- Умные указатели: Box, Rc, RefCell
- Многопоточность
- Асинхронное программирование
- Модули и организация кода
- Макросы
- Тестирование
Rust использует менеджер пакетов и инструментов cargo
, аналогичный Maven или Gradle в Java.
# Установка Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Создание нового проекта
cargo new my_project
# Сборка проекта
cargo build
# Запуск проекта
cargo run
# Тестирование
cargo test
my_project/
├── Cargo.toml # Аналог pom.xml или build.gradle
├── Cargo.lock # Фиксирует версии зависимостей
└── src/
└── main.rs # Точка входа в программу
// Однострочный комментарий
/*
Многострочный комментарий
*/
/// Документационный комментарий для функций, структур и т.д.
/// Поддерживает Markdown
///
/// # Примеры
/// ```
/// let x = add(1, 2);
/// assert_eq!(x, 3);
/// ```
//! Документационный комментарий для модуля
fn main() {
println!("Hello, world!");
}
// Неизменяемая переменная (по умолчанию)
let x = 5;
// Изменяемая переменная
let mut y = 5;
y = 6; // OK
// Константа (должна иметь явный тип и значение, известное на этапе компиляции)
const MAX_POINTS: u32 = 100_000;
// Затенение переменных (shadowing) - создание новой переменной с тем же именем
let z = 5;
let z = z + 1; // Создаёт новую переменную z
let z = z * 2; // Создаёт новую переменную z
В отличие от Java, где переменные по умолчанию изменяемые, в Rust переменные по умолчанию неизменяемые.
// Целые числа
let a: i8 = -128; // 8-битное со знаком
let b: u8 = 255; // 8-битное без знака
let c: i16 = -32768; // 16-битное со знаком
let d: u16 = 65535; // 16-битное без знака
let e: i32 = -2147483648; // 32-битное со знаком (по умолчанию)
let f: u32 = 4294967295; // 32-битное без знака
let g: i64 = -9223372036854775808; // 64-битное со знаком
let h: u64 = 18446744073709551615; // 64-битное без знака
let i: i128 = -170141183460469231731687303715884105728; // 128-битное со знаком
let j: u128 = 340282366920938463463374607431768211455; // 128-битное без знака
let k: isize = -1; // Зависит от архитектуры (32 или 64 бита)
let l: usize = 1; // Зависит от архитектуры (32 или 64 бита)
// Числа с плавающей точкой
let m: f32 = 3.14; // 32-битное с плавающей точкой
let n: f64 = 3.14159265359; // 64-битное с плавающей точкой (по умолчанию)
// Логический тип
let o: bool = true;
// Символы (Unicode)
let p: char = 'a'; // 4 байта (не как в Java, где char - 2 байта)
// Кортежи (Tuples)
let q: (i32, f64, char) = (42, 3.14, 'a');
let (r, s, t) = q; // Деструктуризация
let u = q.0; // Доступ по индексу
// Массивы (фиксированной длины)
let v: [i32; 5] = [1, 2, 3, 4, 5];
let w = v[0]; // Доступ по индексу
// Строки
let x: &str = "Hello"; // Строковый срез (string slice)
let y: String = String::from("Hello"); // Владеющая строка (owned string)
let z = x.to_string(); // Конвертация &str в String
// Явное преобразование с использованием as
let a = 5;
let b = a as f64;
// С использованием методов
let c = "42".parse::<i32>().unwrap(); // Из строки в число
let d = 42.to_string(); // Из числа в строку
// if-else (как выражение)
let number = 6;
if number % 4 == 0 {
println!("Число делится на 4");
} else if number % 3 == 0 {
println!("Число делится на 3");
} else {
println!("Число не делится ни на 4, ни на 3");
}
// if как выражение (аналог тернарного оператора в Java)
let condition = true;
let number = if condition { 5 } else { 6 };
// loop - бесконечный цикл с возможностью возврата значения
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // Возвращает значение
}
};
// while
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
// for с диапазоном
for i in 1..=5 { // Включая 5
println!("{}!", i);
}
// for с итератором
let a = [10, 20, 30, 40, 50];
for element in a.iter() {
println!("значение: {}", element);
}
let number = 13;
match number {
// Сопоставление с одним значением
1 => println!("Один!"),
// Сопоставление с несколькими значениями
2 | 3 | 5 | 7 | 11 | 13 => println!("Простое число!"),
// Сопоставление с диапазоном
15..=19 => println!("От 15 до 19"),
// Захват значения
n @ 20..=30 => println!("От 20 до 30: {}", n),
// Значение по умолчанию
_ => println!("Другое число"),
}
// if let - упрощённый match для одного шаблона
let some_value = Some(3);
if let Some(3) = some_value {
println!("Три!");
}
// while let - цикл, который продолжается, пока шаблон соответствует
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("Верхний элемент: {}", top);
}
// Простая функция
fn say_hello() {
println!("Привет!");
}
// Функция с параметрами
fn add(a: i32, b: i32) -> i32 {
a + b // Отсутствие точки с запятой означает возврат значения
}
// Функция с несколькими возвращаемыми значениями через кортеж
fn calculate_stats(values: &[i32]) -> (i32, i32, i32) {
let sum = values.iter().sum();
let min = *values.iter().min().unwrap_or(&0);
let max = *values.iter().max().unwrap_or(&0);
(sum, min, max)
}
// Простое замыкание
let add_one = |x| x + 1;
let five = add_one(4);
// Замыкание с явными типами
let add_typed = |x: i32, y: i32| -> i32 { x + y };
// Замыкание, захватывающее переменные из окружения
let x = 4;
let equal_to_x = |z| z == x;
let y = 4;
assert!(equal_to_x(y));
// Замыкание как параметр функции
fn apply<F>(f: F, x: i32) -> i32
where F: Fn(i32) -> i32
{
f(x)
}
let double = |x| x * 2;
let result = apply(double, 5); // 10
В Rust есть три трейта для замыканий:
Fn
- замыкание захватывает по ссылке (&T
)FnMut
- замыкание захватывает по изменяемой ссылке (&mut T
)FnOnce
- замыкание захватывает по значению (T
)
// FnOnce - выполняется только один раз, потому что потребляет захваченное значение
fn consume<F>(func: F)
where F: FnOnce() -> String
{
println!("{}", func());
}
let s = String::from("hello");
let consume_and_return = move || s;
consume(consume_and_return);
// consume(consume_and_return); // Ошибка! s уже перемещена
// FnMut - может изменять захваченные значения
fn do_twice<F>(mut func: F)
where F: FnMut()
{
func();
func();
}
let mut counter = 0;
let mut increment = || {
counter += 1;
println!("Counter: {}", counter);
};
do_twice(increment);
// Fn - только читает захваченные значения
fn apply_to_3<F>(f: F) -> i32
where F: Fn(i32) -> i32
{
f(3)
}
let double = |x| x * 2;
println!("3 * 2 = {}", apply_to_3(double));
Основные правила владения:
- Каждое значение в Rust имеет переменную, которая называется его владельцем.
- В каждый момент времени может быть только один владелец.
- Когда владелец выходит из области видимости, значение удаляется.
// Пример перемещения владения (move)
let s1 = String::from("hello");
let s2 = s1; // s1 перемещается в s2
// println!("{}", s1); // Ошибка! s1 больше не действительна
// Клонирование (создание глубокой копии)
let s1 = String::from("hello");
let s2 = s1.clone(); // Создаётся новая копия данных
println!("s1 = {}, s2 = {}", s1, s2); // OK
В Java автоматический сборщик мусора отслеживает и очищает неиспользуемую память, в Rust память освобождается автоматически, когда владелец выходит из области видимости.
// Заимствование по ссылке (неизменяемое)
fn calculate_length(s: &String) -> usize {
s.len()
}
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("Длина '{}' равна {}.", s1, len); // s1 всё ещё действительна
// Заимствование по изменяемой ссылке
fn change(s: &mut String) {
s.push_str(", world");
}
let mut s = String::from("hello");
change(&mut s);
println!("{}", s); // "hello, world"
// Правила заимствования:
// 1. В любой момент времени может быть ЛИБО одна изменяемая ссылка,
// ЛИБО любое количество неизменяемых ссылок.
// 2. Ссылки всегда должны быть действительными.
let mut s = String::from("hello");
let r1 = &s; // OK
let r2 = &s; // OK
// let r3 = &mut s; // Ошибка! Нельзя иметь изменяемую ссылку при наличии неизменяемых
println!("{} and {}", r1, r2);
// r1 и r2 больше не используются после этой точки
let r3 = &mut s; // OK, т.к. r1 и r2 больше не используются
println!("{}", r3);
Времена жизни - это способ, которым Rust гарантирует, что все ссылки действительны в течение всего времени их использования.
// Явное указание времени жизни
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
let string1 = String::from("долгая строка");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
println!("Наибольшая строка: {}", result); // OK
}
// println!("Наибольшая строка: {}", result); // Ошибка! string2 уже не существует
// Времена жизни в структурах
struct ImportantExcerpt<'a> {
part: &'a str,
}
let novel = String::from("Жили-были. Они жили счастливо.");
let first_sentence = novel.split('.').next().unwrap();
let excerpt = ImportantExcerpt {
part: first_sentence,
};
Правила вывода времён жизни:
- Каждый входной параметр-ссылка получает своё время жизни.
- Если есть только один входной параметр-ссылка, его время жизни назначается всем выходным параметрам-ссылкам.
- Если есть несколько входных параметров-ссылок, но один из них
&self
или&mut self
, время жизниself
назначается всем выходным параметрам-ссылкам.
// Обычная структура
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
// Создание экземпляра
let user1 = User {
email: String::from("[email protected]"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
// Изменение полей (структура должна быть изменяемой)
let mut user2 = User {
email: String::from("[email protected]"),
username: String::from("anotherusername567"),
active: true,
sign_in_count: 1,
};
user2.email = String::from("[email protected]");
// Краткий синтаксис создания
fn build_user(email: String, username: String) -> User {
User {
email, // Вместо email: email
username, // Вместо username: username
active: true,
sign_in_count: 1,
}
}
// Создание из другого экземпляра с обновлением
let user3 = User {
email: String::from("[email protected]"),
..user1 // Остальные поля берутся из user1
};
// Кортежные структуры (без именованных полей)
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
// Unit-подобные структуры (без полей)
struct AlwaysEqual;
let subject = AlwaysEqual;
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// Метод (принимает self)
fn area(&self) -> u32 {
self.width * self.height
}
// Метод с изменяемым self
fn resize(&mut self, width: u32, height: u32) {
self.width = width;
self.height = height;
}
// Ассоциированная функция (не принимает self)
// Аналог статического метода в Java
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
let mut rect = Rectangle {
width: 30,
height: 50,
};
println!("Площадь: {}", rect.area());
rect.resize(60, 70);
let square = Rectangle::square(20);
// Простое перечисление
enum Direction {
North,
South,
East,
West,
}
// Перечисление с данными
enum Message {
Quit, // Без данных
Move { x: i32, y: i32 }, // Именованные поля как в структуре
Write(String), // Строка
ChangeColor(i32, i32, i32), // Кортеж из трёх i32
}
// Использование
let msg = Message::Write(String::from("hello"));
// Перечисление с методами
impl Message {
fn call(&self) {
// Метод для обработки сообщения
match self {
Message::Quit => println!("Выход"),
Message::Move { x, y } => println!("Перемещение на x: {}, y: {}", x, y),
Message::Write(text) => println!("Текстовое сообщение: {}", text),
Message::ChangeColor(r, g, b) => println!("Изменение цвета на {}, {}, {}", r, g, b),
}
}
}
let m = Message::Write(String::from("hello"));
m.call();
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// ...и т.д.
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
}
}
}
Option<T>
- перечисление для представления возможного отсутствия значения (аналог Optional<T>
в Java).
enum Option<T> {
Some(T),
None,
}
// Использование Option
let some_number = Some(5);
let some_string = Some("строка");
let absent_number: Option<i32> = None;
// Извлечение значения с unwrap (вызывает панику при None)
let x = some_number.unwrap();
// Безопасное извлечение с проверкой
match some_number {
Some(i) => println!("Значение: {}", i),
None => println!("Значение отсутствует"),
}
// Использование if let
if let Some(value) = some_string {
println!("Строка: {}", value);
}
// Методы Option
let x = Some(5);
let y = x.map(|n| n * 2); // Some(10)
let z = None.map(|n: i32| n * 2); // None
let a = Some(5).filter(|n| n > &3); // Some(5)
let b = Some(2).filter(|n| n > &3); // None
let c = Some("hello").unwrap_or("world"); // "hello"
let d = None.unwrap_or("world"); // "world"
let e = Some(5).and_then(|n| Some(n * 2)); // Some(10)
let f = None.and_then(|n: i32| Some(n * 2)); // None
Result<T, E>
- перечисление для представления результата, который может быть ошибкой.
enum Result<T, E> {
Ok(T),
Err(E),
}
// Использование Result
use std::fs::File;
use std::io::{self, Read};
fn read_file() -> Result<String, io::Error> {
let mut file = match File::open("hello.txt") {
Ok(file) => file,
Err(error) => return Err(error),
};
let mut contents = String::new();
match file.read_to_string(&mut contents) {
Ok(_) => Ok(contents),
Err(error) => Err(error),
}
}
// Сокращённая запись с оператором ?
fn read_file_short() -> Result<String, io::Error> {
let mut file = File::open("hello.txt")?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
// Ещё короче
fn read_file_shortest() -> Result<String, io::Error> {
let mut contents = String::new();
File::open("hello.txt")?.read_to_string(&mut contents)?;
Ok(contents)
}
// Методы Result
let x: Result<i32, &str> = Ok(5);
let y: Result<i32, &str> = Err("Ошибка");
println!("{}", x.unwrap()); // 5
// println!("{}", y.unwrap()); // Паника с сообщением "Ошибка"
println!("{}", x.unwrap_or(0)); // 5
println!("{}", y.unwrap_or(0)); // 0
let z = x.map(|n| n * 2); // Ok(10)
let w = y.map(|n| n * 2); // Err("Ошибка")
let a = x.map_err(|e| format!("Произошла ошибка: {}", e)); // Ok(5)
let b = y.map_err(|e| format!("Произошла ошибка: {}", e)); // Err("Произошла ошибка: Ошибка")
let c = x.and_then(|n| Ok(n * 2)); // Ok(10)
let d = y.and_then(|n| Ok(n * 2)); // Err("Ошибка")
panic!
- макрос для аварийного завершения программы при неисправимых ошибках.
// Вызов паники
fn divide(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("Деление на ноль!");
}
a / b
}
// Настройка поведения паники в Cargo.toml
// [profile.release]
// panic = 'abort' // Немедленное завершение без раскрутки стека
Трейты в Rust похожи на интерфейсы в Java, но с некоторыми дополнительными возможностями.
// Определение трейта
trait Summary {
// Метод без реализации
fn summarize(&self) -> String;
// Метод с реализацией по умолчанию
fn default_summary(&self) -> String {
String::from("(Читать дальше...)")
}
}
// Структуры
struct NewsArticle {
headline: String,
location: String,
author: String,
content: String,
}
struct Tweet {
username: String,
content: String,
reply: bool,
retweet: bool,
}
// Реализация трейта для структуры
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, автор {} ({})", self.headline, self.author, self.location)
}
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
// Переопределение метода с реализацией по умолчанию
fn default_summary(&self) -> String {
format!("Твит от {}", self.username)
}
}
// Использование
let article = NewsArticle {
headline: String::from("Заголовок!"),
location: String::from("Москва"),
author: String::from("Иван Иванов"),
content: String::from("Содержание статьи..."),
};
let tweet = Tweet {
username: String::from("user123"),
content: String::from("Привет, мир!"),
reply: false,
retweet: false,
};
println!("Статья: {}", article.summarize());
println!("Статья по умолчанию: {}", article.default_summary());
println!("Твит: {}", tweet.summarize());
println!("Твит по умолчанию: {}", tweet.default_summary());
// Функция, принимающая любой тип, реализующий трейт Summary
fn notify(item: &impl Summary) {
println!("Срочные новости! {}", item.summarize());
}
// Эквивалентная запись с использованием ограничений типов
fn notify_generic<T: Summary>(item: &T) {
println!("Срочные новости! {}", item.summarize());
}
// Несколько ограничений типов
fn notify_multiple<T: Summary + Display>(item: &T) {
println!("Срочные новости! {}", item.summarize());
println!("Отображение: {}", item);
}
// Синтаксис where для сложных ограничений
fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
// реализация
}
fn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from("конечно, как вы, наверное, знаете, люди"),
reply: false,
retweet: false,
}
}
Ограничение: функция может возвращать только один конкретный тип, реализующий трейт. Например, следующий код не скомпилируется:
// Ошибка! Возвращаемый тип должен быть одним конкретным типом
fn returns_summarizable(switch: bool) -> impl Summary {
if switch {
NewsArticle {
headline: String::from("Заголовок"),
location: String::from("Москва"),
author: String::from("Автор"),
content: String::from("Содержание"),
}
} else {
Tweet {
username: String::from("horse_ebooks"),
content: String::from("контент"),
reply: false,
retweet: false,
}
}
}
Для таких случаев можно использовать паттерн "динамическая диспетчеризация" с трейт-объектами:
fn returns_summarizable(switch: bool) -> Box<dyn Summary> {
if switch {
Box::new(NewsArticle {
headline: String::from("Заголовок"),
location: String::from("Москва"),
author: String::from("Автор"),
content: String::from("Содержание"),
})
} else {
Box::new(Tweet {
username: String::from("horse_ebooks"),
content: String::from("контент"),
reply: false,
retweet: false,
})
}
}
// Вектор трейт-объектов (аналог полиморфизма в Java)
let articles: Vec<Box<dyn Summary>> = vec![
Box::new(NewsArticle {
headline: String::from("Заголовок"),
location: String::from("Москва"),
author: String::from("Автор"),
content: String::from("Содержание"),
}),
Box::new(Tweet {
username: String::from("horse_ebooks"),
content: String::from("контент"),
reply: false,
retweet: false,
}),
];
// Вызов методов для каждого элемента
for article in &articles {
println!("{}", article.summarize());
}
Маркерные трейты не имеют методов и используются для добавления свойств типам:
// Встроенные маркерные трейты
// Send - тип может безопасно передаваться между потоками
// Sync - тип может безопасно использоваться из нескольких потоков
// Copy - тип может быть скопирован с помощью memcpy
// Sized - тип имеет известный размер во время компиляции
// Пользовательский маркерный трейт
trait Printable {}
impl Printable for i32 {}
impl Printable for String {}
fn print_item<T: Printable + std::fmt::Debug>(item: T) {
println!("{:?}", item);
}
Дженерики в Rust похожи на дженерики в Java, но с более мощными ограничениями типов.
// Функция с одним дженерик-параметром
fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
let numbers = vec![34, 50, 25, 100, 65];
let result = largest(&numbers);
println!("Наибольшее число: {}", result);
let chars = vec!['y', 'm', 'a', 'q'];
let result = largest(&chars);
println!("Наибольший символ: {}", result);
// Структура с одним дженерик-параметром
struct Point<T> {
x: T,
y: T,
}
// Структура с разными дженерик-параметрами
struct MixedPoint<T, U> {
x: T,
y: U,
}
// Использование
let integer_point = Point { x: 5, y: 10 };
let float_point = Point { x: 1.0, y: 4.0 };
let mixed_point = MixedPoint { x: 5, y: 4.0 };
// Методы для дженерик-структуры
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
// Методы только для конкретного типа
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
// Методы с дополнительными дженерик-параметрами
impl<T, U> MixedPoint<T, U> {
fn mixup<V, W>(self, other: MixedPoint<V, W>) -> MixedPoint<T, W> {
MixedPoint {
x: self.x,
y: other.y,
}
}
}
// Несколько ограничений
fn print_and_compare<T: std::fmt::Display + std::cmp::PartialOrd>(a: T, b: T) {
println!("a = {}, b = {}", a, b);
if a > b {
println!("a больше b");
} else {
println!("a не больше b");
}
}
// Синтаксис where для сложных ограничений
fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: std::fmt::Display + Clone,
U: Clone + std::fmt::Debug,
{
println!("t = {}", t);
println!("u = {:?}", u);
42
}
Rust выполняет монотипизацию во время компиляции, создавая конкретные реализации для каждого используемого типа. Это означает отсутствие накладных расходов на дженерики во время выполнения (в отличие от Java, где есть стирание типов).
// Создание пустого вектора
let mut v: Vec<i32> = Vec::new();
// Создание с начальными значениями
let v = vec![1, 2, 3, 4, 5];
// Добавление элементов
v.push(6);
v.push(7);
// Чтение элементов
let third: &i32 = &v[2]; // Может вызвать панику при выходе за границы
let third: Option<&i32> = v.get(2); // Безопасный доступ
// Перебор элементов
for i in &v {
println!("{}", i);
}
// Перебор с изменением
for i in &mut v {
*i += 50;
}
// Использование enum для хранения разных типов
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("синий")),
SpreadsheetCell::Float(10.12),
];
// Создание новой строки
let mut s = String::new();
// Создание из строкового литерала
let s = String::from("начальное содержимое");
let s = "начальное содержимое".to_string();
// Добавление к строке
let mut s = String::from("foo");
s.push_str("bar"); // foobar
s.push('!'); // foobar!
// Конкатенация
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // s1 перемещена и больше не действительна
// Форматирование
let s = format!("{}-{}-{}", "tic", "tac", "toe"); // tic-tac-toe
// Индексирование (не работает напрямую из-за UTF-8)
// let c = s[0]; // Ошибка!
// Срезы строк
let hello = "Здравствуйте";
let s = &hello[0..4]; // Первые 4 байта (не символа!)
// Итерация по символам
for c in "Зд".chars() {
println!("{}", c);
}
// Итерация по байтам
for b in "Зд".bytes() {
println!("{}", b);
}
use std::collections::HashMap;
// Создание пустой хеш-таблицы
let mut scores = HashMap::new();
// Вставка значений
scores.insert(String::from("Синяя"), 10);
scores.insert(String::from("Жёлтая"), 50);
// Создание из векторов
let teams = vec![String::from("Синяя"), String::from("Жёлтая")];
let initial_scores = vec![10, 50];
let scores: HashMap<_, _> = teams.into_iter().zip(initial_scores.into_iter()).collect();
// Доступ к значениям
let team_name = String::from("Синяя");
let score = scores.get(&team_name); // Option<&i32>
// Перебор пар ключ-значение
for (key, value) in &scores {
println!("{}: {}", key, value);
}
// Обновление значения
scores.insert(String::from("Синяя"), 25); // Перезаписывает существующее значение
// Вставка только если ключ отсутствует
scores.entry(String::from("Жёлтая")).or_insert(50);
scores.entry(String::from("Зелёная")).or_insert(50);
// Обновление на основе старого значения
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
// Создание итератора
let v = vec![1, 2, 3];
let mut iter = v.iter(); // Итератор по ссылкам &T
// Ручная итерация
assert_eq!(iter.next(), Some(&1));
assert_eq!(iter.next(), Some(&2));
assert_eq!(iter.next(), Some(&3));
assert_eq!(iter.next(), None);
// Итераторы по значению
let v = vec![1, 2, 3];
let iter = v.into_iter(); // Потребляет коллекцию, возвращает T
let iter = v.iter_mut(); // Итератор по изменяемым ссылкам &mut T
// Методы-потребители (consuming adaptors)
let v = vec![1, 2, 3];
let total: i32 = v.iter().sum(); // 6
// Методы-итераторы (iterator adaptors)
let v = vec![1, 2, 3];
let v2: Vec<_> = v.iter().map(|x| x + 1).collect(); // [2, 3, 4]
// Фильтрация
let v = vec![1, 2, 3, 4, 5];
let even: Vec<_> = v.iter().filter(|&&x| x % 2 == 0).collect(); // [2, 4]
// Цепочка методов
let sum: i32 = v.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * x)
.sum(); // 2*2 + 4*4 = 20
Box<T>
- самый простой умный указатель для размещения данных в куче.
// Создание Box
let b = Box::new(5);
println!("b = {}", b);
// Рекурсивные типы данных с Box
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
Rc<T>
(Reference Counting) - умный указатель для случаев, когда значение может иметь несколько владельцев.
use std::rc::Rc;
enum List {
Cons(i32, Rc<List>),
Nil,
}
use List::{Cons, Nil};
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("Счётчик ссылок после создания a = {}", Rc::strong_count(&a)); // 1
let b = Cons(3, Rc::clone(&a));
println!("Счётчик ссылок после создания b = {}", Rc::strong_count(&a)); // 2
{
let c = Cons(4, Rc::clone(&a));
println!("Счётчик ссылок после создания c = {}", Rc::strong_count(&a)); // 3
}
println!("Счётчик ссылок после выхода c из области видимости = {}", Rc::strong_count(&a)); // 2
RefCell<T>
- умный указатель для внутренней изменяемости, проверяющий правила заимствования во время выполнения, а не компиляции.
use std::cell::RefCell;
// Структура с внутренней изменяемостью
struct MockMessenger {
sent_messages: RefCell<Vec<String>>,
}
impl MockMessenger {
fn new() -> MockMessenger {
MockMessenger {
sent_messages: RefCell::new(vec![]),
}
}
fn send_message(&self, message: &str) {
// Хотя self неизменяемый, мы можем изменить содержимое sent_messages
self.sent_messages.borrow_mut().push(String::from(message));
}
}
let mock = MockMessenger::new();
mock.send_message("hello");
mock.send_message("world");
assert_eq!(mock.sent_messages.borrow().len(), 2);
use std::rc::Rc;
use std::cell::RefCell;
// Структура с множественным владением и внутренней изменяемостью
#[derive(Debug)]
enum List {
Cons(Rc<RefCell<i32>>, Rc<List>),
Nil,
}
use List::{Cons, Nil};
let value = Rc::new(RefCell::new(5));
let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a));
// Изменение значения через любую ссылку
*value.borrow_mut() += 10;
println!("a после = {:?}", a); // Значение в a теперь 15
println!("b после = {:?}", b); // b также видит изменение
println!("c после = {:?}", c); // c также видит изменение
use std::thread;
use std::time::Duration;
// Создание потока
let handle = thread::spawn(|| {
for i in 1..10 {
println!("привет {} из порожденного потока!", i);
thread::sleep(Duration::from_millis(1));
}
});
// Код в основном потоке
for i in 1..5 {
println!("привет {} из основного потока!", i);
thread::sleep(Duration::from_millis(1));
}
// Ожидание завершения порожденного потока
handle.join().unwrap();
// Захват переменных из окружения с помощью move
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Вот вектор: {:?}", v);
});
handle.join().unwrap();
use std::thread;
use std::sync::mpsc;
// Создание канала (multiple producer, single consumer)
let (tx, rx) = mpsc::channel();
// Создание потока-отправителя
thread::spawn(move || {
let val = String::from("привет");
tx.send(val).unwrap();
// val теперь недоступна, т.к. перемещена
});
// Получение значения в основном потоке
let received = rx.recv().unwrap();
println!("Получено: {}", received);
// Отправка нескольких значений
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let vals = vec![
String::from("привет"),
String::from("из"),
String::from("потока"),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
// Получение значений как итератора
for received in rx {
println!("Получено: {}", received);
}
// Несколько отправителей
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone();
thread::spawn(move || {
tx.send(String::from("привет от tx")).unwrap();
});
thread::spawn(move || {
tx1.send(String::from("привет от tx1")).unwrap();
});
for received in rx {
println!("Получено: {}", received);
}
use std::sync::{Mutex, Arc};
use std::thread;
// Мьютекс для безопасного доступа к данным
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6;
}
println!("m = {:?}", m);
// Мьютекс с несколькими потоками
let counter = Arc::new(Mutex::new(0)); // Arc - атомарный Rc для потоков
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Результат: {}", *counter.lock().unwrap());
use futures::executor::block_on;
// Асинхронная функция
async fn hello_world() {
println!("hello, world!");
}
// Выполнение асинхронной функции
fn main() {
let future = hello_world(); // Не выполняется сразу
block_on(future); // Ожидает завершения future
}
// Асинхронные функции, возвращающие значения
async fn get_number() -> u32 {
42
}
async fn print_number() {
let number = get_number().await;
println!("Число: {}", number);
}
// Параллельное выполнение futures
use futures::join;
async fn parallel_tasks() {
let task1 = async {
println!("Задача 1");
};
let task2 = async {
println!("Задача 2");
};
join!(task1, task2);
}
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() {
// Запуск задачи в фоновом режиме
let handle = tokio::spawn(async {
sleep(Duration::from_secs(1)).await;
println!("Задача завершена");
"результат"
});
// Ожидание результата
let result = handle.await.unwrap();
println!("Получен результат: {}", result);
// Параллельные задачи
let mut tasks = vec![];
for i in 0..5 {
let task = tokio::spawn(async move {
sleep(Duration::from_millis(100 * i)).await;
println!("Задача {} завершена", i);
i
});
tasks.push(task);
}
// Ожидание всех задач
for task in tasks {
let result = task.await.unwrap();
println!("Результат: {}", result);
}
}
// src/lib.rs или src/main.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
// Использование модулей
pub fn eat_at_restaurant() {
// Абсолютный путь
crate::front_of_house::hosting::add_to_waitlist();
// Относительный путь
front_of_house::hosting::add_to_waitlist();
}
src/
├── main.rs // точка входа бинарного проекта
├── lib.rs // точка входа библиотеки
├── front_of_house.rs // модуль front_of_house
└── front_of_house/
├── hosting.rs // подмодуль hosting
└── serving.rs // подмодуль serving
// src/lib.rs
mod front_of_house;
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
// src/front_of_house.rs
pub mod hosting;
mod serving;
// src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}
fn seat_at_table() {}
// Импорт отдельных элементов
use std::collections::HashMap;
use std::io::{self, Write};
// Импорт всех публичных элементов
use std::collections::*;
// Переименование при импорте
use std::io::Result as IoResult;
// Реэкспорт (публичный импорт)
pub use crate::front_of_house::hosting;
mod back_of_house {
// Публичная структура с некоторыми приватными полями
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("персики"),
}
}
}
// Публичное перечисление с публичными вариантами
pub enum Appetizer {
Soup,
Salad,
}
}
pub fn eat_at_restaurant() {
let mut meal = back_of_house::Breakfast::summer("Ржаной");
meal.toast = String::from("Пшеничный");
// meal.seasonal_fruit = String::from("черника"); // Ошибка! Приватное поле
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
Макросы в Rust - это способ метапрограммирования, позволяющий писать код, который генерирует другой код.
// Определение макроса
macro_rules! say_hello {
// Шаблон без аргументов
() => {
println!("Привет!");
};
// Шаблон с одним аргументом
($name:expr) => {
println!("Привет, {}!", $name);
};
}
// Использование макроса
say_hello!(); // Привет!
say_hello!("Rust"); // Привет, Rust!
// Макрос с переменным числом аргументов
macro_rules! vec_of_strings {
($($x:expr),*) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x.to_string());
)*
temp_vec
}
};
}
let v = vec_of_strings![1, 2, 3]; // ["1", "2", "3"]
Процедурные макросы действуют как функции, принимающие и возвращающие токены Rust. Есть три типа:
- Атрибутоподобные макросы
- Производные макросы
- Функциональные макросы
// Пример атрибутоподобного макроса
use proc_macro::TokenStream;
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
// Реализация
}
// Использование
#[route(GET, "/")]
fn index() {
// ...
}
// Пример производного макроса
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
// Реализация
}
// Использование
#[derive(HelloMacro)]
struct Pancakes;
// Пример функционального макроса
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
// Реализация
}
// Использование
let sql = sql!(SELECT * FROM posts WHERE id=1);
// В файле с кодом
pub fn add_two(a: i32) -> i32 {
a + 2
}
// Модуль тестов
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(4, add_two(2));
}
#[test]
fn it_fails() {
assert_eq!(5, add_two(2));
}
// Тест с сообщением об ошибке
#[test]
fn it_works_with_message() {
let result = add_two(2);
assert_eq!(result, 4, "2 + 2 должно равняться 4, получено {}", result);
}
// Проверка на панику
#[test]
#[should_panic]
fn it_panics() {
panic!("Этот тест должен вызвать панику");
}
// Проверка на конкретное сообщение паники
#[test]
#[should_panic(expected = "Деление на ноль")]
fn it_panics_with_message() {
divide(10, 0);
}
// Тест с Result
#[test]
fn it_works_result() -> Result<(), String> {
if add_two(2) == 4 {
Ok(())
} else {
Err(String::from("2 + 2 не равно 4"))
}
}
// Игнорирование теста
#[test]
#[ignore]
fn expensive_test() {
// Долгий тест, который мы хотим пропустить
}
}
Структура каталогов для интеграционных тестов:
my_crate/
├── Cargo.toml
├── src/
│ └── lib.rs
└── tests/
├── integration_test.rs
├── common/
│ └── mod.rs
└── api_tests/
└── api.rs
Пример интеграционного теста:
// tests/integration_test.rs
// Импорт библиотеки
use my_crate;
// Вспомогательная функция
mod common;
#[test]
fn it_adds_two() {
common::setup();
assert_eq!(4, my_crate::add_two(2));
}
Вспомогательный модуль для интеграционных тестов:
// tests/common/mod.rs
pub fn setup() {
// Код настройки, общий для нескольких тестов
}
# Запуск всех тестов
cargo test
# Запуск конкретного теста
cargo test it_works
# Запуск тестов, содержащих определённую строку в имени
cargo test add
# Запуск игнорируемых тестов
cargo test -- --ignored
# Запуск всех тестов, включая игнорируемые
cargo test -- --include-ignored
# Вывод stdout во время тестов
cargo test -- --nocapture
# Запуск тестов в одном потоке
cargo test -- --test-threads=1
# Запуск только интеграционных тестов
cargo test --test integration_test
Rust позволяет писать тесты прямо в документации:
/// Adds two to the number given.
///
/// # Examples
///
/// ```
/// let result = my_crate::add_two(2);
/// assert_eq!(result, 4);
/// ```
pub fn add_two(a: i32) -> i32 {
a + 2
}
Запуск документационных тестов:
cargo test --doc
Концепция | Java | Rust |
---|---|---|
Управление памятью | Сборщик мусора | Система владения с временами жизни |
Многопоточность | Потоки, синхронизация | Потоки, каналы, Mutex, Arc |
Обработка ошибок | Исключения | Result и Option |
Обобщённые типы | Дженерики с стиранием типов | Дженерики с монотипизацией |
Наследование | Классы и интерфейсы | Трейты и композиция |
Null-значения | null | Option |
Изменяемость | По умолчанию изменяемые | По умолчанию неизменяемые |
Проверка типов | Во время компиляции | Во время компиляции |
Java | Rust |
---|---|
public class MyClass {} |
pub struct MyClass {} |
interface MyInterface {} |
trait MyTrait {} |
enum Color { RED, GREEN, BLUE } |
enum Color { Red, Green, Blue } |
Optional<T> |
Option<T> |
try/catch |
Result<T, E> с ? оператором |
ArrayList<T> |
Vec<T> |
HashMap<K, V> |
HashMap<K, V> |
String |
String |
String.format("%s %d", s, n) |
format!("{} {}", s, n) |
public static void main(String[] args) |
fn main() |
for (int i = 0; i < 10; i++) |
for i in 0..10 |
for (Item item : items) |
for item in &items |
items.stream().map(...).filter(...) |
items.iter().map(...).filter(...) |
new Thread(() -> { ... }).start() |
`thread::spawn( |
synchronized (obj) { ... } |
let _guard = mutex.lock().unwrap(); ... |
-
Безопасность памяти без сборщика мусора: Rust обеспечивает безопасность памяти без накладных расходов сборщика мусора.
-
Отсутствие null-указателей: Использование
Option<T>
вместо null устраняет ошибки NullPointerException. -
Контроль над изменяемостью: Неизменяемость по умолчанию помогает избежать многих ошибок.
-
Выразительная система типов: Перечисления с данными, сопоставление с образцом и времена жизни делают код более надёжным.
-
Производительность: Rust обычно работает быстрее Java благодаря отсутствию виртуальной машины и сборщика мусора.
-
Отсутствие исключений: Явная обработка ошибок с помощью
Result<T, E>
делает код более предсказуемым. -
Макросы: Мощная система макросов позволяет писать более выразительный код.
-
Низкоуровневый контроль: Rust позволяет работать с памятью напрямую, когда это необходимо.
-
Система владения и заимствования: Самая сложная концепция для новичков в Rust.
-
Времена жизни: Требуют понимания и практики.
-
Отсутствие наследования классов: Необходимо привыкнуть к композиции и трейтам.
-
Явная обработка ошибок: Требует больше кода, но делает его надёжнее.
-
Компилятор как строгий учитель: Rust имеет более строгие правила, которые могут казаться ограничивающими.
Rust - мощный и современный язык программирования, который предлагает уникальное сочетание безопасности, производительности и выразительности. Хотя переход с Java на Rust может быть сложным из-за фундаментальных различий в подходе к управлению памятью и обработке ошибок, инвестиции в изучение Rust могут окупиться повышенной производительностью, безопасностью и надёжностью вашего кода.
Ключ к успешному освоению Rust - это понимание системы владения и заимствования, а также принятие идиоматических подходов языка вместо попыток писать код в стиле Java. С практикой и терпением вы сможете использовать все преимущества Rust, создавая быстрый, безопасный и надёжный код.