Утро: код, кофе и компилятор
В мире системного программирования Rust занимает особое место. Он сочетает скорость C++ с безопасностью памяти, которую гарантирует компилятор на уровне типов. Но как на самом деле выглядит рабочий день разработчика, который пишет на Rust? Забудьте про стереотипы о «скучных системных программистах» — сегодня мы разберем реальный день из жизни Rust-инженера. Без прикрас, с реальными кусками кода и живыми проблемами.
Представьте: 9:00 утра. Вы открываете ноутбук, запускаете терминал и видите сообщение от CI: «Build failed». Вздыхаете, открываете cargo check и погружаетесь в мир borrow checker'а. Добро пожаловать в день Rust-разработчика.
Стандартное утро: код-ревью и тикеты
Первым делом — проверка пулл-реквестов. В Rust-командах код-ревью особенно важно, потому что компилятор ловит много ошибок, но логику и архитектуру проверяют люди. Вы смотрите на PR коллеги, который добавляет новый модуль для обработки конфигурации.
// config.rs — парсинг конфигурации
use serde::Deserialize;
use std::path::Path;
#[derive(Debug, Deserialize)]
struct AppConfig {
host: String,
port: u16,
max_connections: u32,
db_url: String,
}
fn load_config(path: impl AsRef<Path>) -> Result<AppConfig, ConfigError> {
let content = std::fs::read_to_string(path.as_ref())?;
let config: AppConfig = toml::from_str(&content)?;
Ok(config)
}
Вы замечаете: нет проверки на пустые строки. Добавляете комментарий: «А что если host будет пустым? Может, добавить валидацию через validator?» Через 15 минут коллега отвечает: «Согласен, добавил #[validate]». День начинается продуктивно.
Основная часть: погружение в код
10:00 — Работа с асинхронностью и tokio
Вы переключаетесь на свою задачу: нужно переписать HTTP-сервер, который обрабатывает вебхуки. Старый код написан на блокирующих потоках, и он тормозит. Ваш инструмент — tokio и async/await. Rust отлично подходит для высоконагруженных систем, и вы это знаете.
Пишете новый обработчик:
use axum::{
routing::post,
Router,
Json,
};
use serde_json::Value;
async fn handle_webhook(Json(payload): Json<Value>) -> String {
// Валидация, обработка, запись в БД
println!("Получен вебхук: {:?}", payload);
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
"OK".to_string()
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/webhook", post(handle_webhook));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
Вы запускаете cargo run — ошибок нет. Rust снова доказал свою надежность. Но это только начало.
11:30 — Битва с borrow checker'ом
Внезапно приходит баг-репорт: «Паника при обработке списка пользователей». Вы открываете код и видите проблему: указатель &mut используется в нескольких местах. Rust не даст вам выстрелить себе в ногу, но иногда борется с вами за право владения.
Вот проблемный фрагмент:
fn process_users(users: &mut Vec<User>) {
let admin = users.iter_mut().find(|u| u.role == Role::Admin).unwrap();
admin.do_task(); // заимствование mutable
// а тут мы снова хотим использовать users
let count = users.len(); // ОШИБКА! users уже заимствован
}
Вы исправляете, разделяя логику:
fn process_users(users: &mut Vec<User>) {
// Сначала находим индекс, потом работаем
if let Some(admin_idx) = users.iter().position(|u| u.role == Role::Admin) {
let admin = &mut users[admin_idx];
admin.do_task();
}
// Теперь можно безопасно использовать users
let count = users.len();
println!("Всего пользователей: {}", count);
}
Компилятор доволен. Вы тоже. Rust учит вас думать о времени жизни данных — это делает код чище.
13:00 — Обед и рефлексия
За обедом вы листаете Reddit и читаете про новые фичи Rust 2024 edition: стабилизация async fn in traits и улучшения в cargo. Решаете, что пора обновить проект. Rust-сообщество активно развивается, и вы стараетесь быть в курсе.
14:00 — Работа с внешними библиотеками и unsafe
После обеда — новая задача: нужно интегрировать C-библиотеку для работы с GPU. Rust умеет вызывать C-функции через FFI. Но это требует unsafe — зона ответственности.
Вы пишете обертку:
extern "C" {
fn gpu_compute(data: *const f32, len: usize) -> f32;
}
fn safe_gpu_compute(data: &[f32]) -> Result<f32, String> {
if data.is_empty() {
return Err("Пустой массив".into());
}
unsafe {
let result = gpu_compute(data.as_ptr(), data.len());
Ok(result)
}
}
Вы тщательно проверяете границы: Rust не прощает ошибок в unsafe. Каждый раз, когда вы пишете unsafe, вы чувствуете себя как сапер — но это дает невероятную производительность.
15:30 — Тестирование и CI/CD
Пора запускать тесты. В Rust тесты пишутся прямо в коде с атрибутом #[test]. Вы запускаете:
cargo test -- --nocapture
Видите зеленые галочки. Но CI/CD требует большего: вы добавляете бенчмарки на criterion и проверяете покрытие кода через tarpaulin. Rust-экосистема богата инструментами.
Пример бенчмарка:
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn fibonacci(n: u64) -> u64 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
fn bench_fib(c: &mut Criterion) {
c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));
}
criterion_group!(benches, bench_fib);
criterion_main!(benches);
Результат: 10 микросекунд. Неплохо, но можно ускорить итеративно. Вы оставляете TODO.
17:00 — Деплой и мониторинг
Последняя задача дня —