Время в миллисекундах в Rust: не попадись в эти 5 ловушек
«Как мне получить текущее время в миллисекундах?» — этот вопрос из Stack Overflow собрал тысячи просмотров. Кажется, что может быть проще? Но Rust — не Python и не JavaScript. Здесь легко спутать UNIX timestamp с монотонным временем, забыть про платформозависимость или наступить на грабли с производительностью.
В этой статье мы разберём топ ошибок, которые делают новички (и не только) при работе со временем в Rust. Вы узнаете, как получить время в миллисекундах правильно, надёжно и быстро. Поехали!
Ошибка №1: Монотонное время вместо UNIX-времени
Самая распространённая ловушка — использовать std::time::Instant для получения «текущего времени». Instant в Rust измеряет монотонное время — оно идеально для замеров интервалов, но абсолютно бесполезно, если вам нужен timestamp (например, для логирования или отправки на сервер).
// ❌ ОШИБКА: так timestamp не получить
use std::time::Instant;
let now = Instant::now();
// У Instant нет метода duration_since(UNIX_EPOCH)!
// Вы получите ошибку компиляции
Как правильно? Используйте std::time::SystemTime. Этот тип знает о системном времени и может отсчитывать от UNIX_EPOCH (1 января 1970 года).
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Время пошло вспять?");
let millis = now.as_millis(); // u128
println!("Текущее время в мс: {}", millis);
💡 Совет: duration_since возвращает Result, потому что теоретически системное время может быть установлено раньше 1970 года. В современных системах это исключение можно смело обрабатывать через expect или unwrap.
Ошибка №2: Забыть про платформозависимость
Казалось бы, SystemTime::now() — это кроссплатформенное решение. Но есть нюанс. На некоторых платформах (например, старые ядра Linux или встроенные системы) SystemTime может вести себя нестабильно или вовсе не поддерживать точность до миллисекунд.
Второй подводный камень — переполнение. Тип u128 в as_millis() вряд ли переполнится (хватит на миллиарды лет), но если вам нужно сохранять время в базу данных или передавать по сети — миллисекунды в u128 могут быть неожиданностью для других систем.
// ✅ Безопасное приведение к u64 (хватит до ~584 млн лет)
let millis_u64: u64 = now.as_millis() as u64;
Как избежать? Всегда проверяйте документацию для вашей целевой платформы. Для встраиваемых систем рассмотрите крейт embassy-time или std::time с осторожностью.
Ошибка №3: Игнорирование крейта chrono
Стандартная библиотека Rust даёт базовые возможности. Но если вам нужно работать с часовыми поясами, форматированием или парсингом дат — не изобретайте велосипед. Крейт chrono — стандарт де-факто для работы с датой и временем в Rust.
// Добавьте в Cargo.toml: chrono = "0.4"
use chrono::Utc;
let now = Utc::now();
let millis = now.timestamp_millis(); // i64
println!("Chrono timestamp: {}", millis);
⚠️ Важно: timestamp_millis() возвращает i64, а не u128. Это сделано для совместимости с UNIX-временем (оно может быть отрицательным для дат до 1970 года). В большинстве случаев i64 — это то, что нужно.
💡 Когда использовать chrono?
- Нужно получить дату в формате
"2025-01-15" - Работа с часовыми поясами (Local, UTC, FixedOffset)
- Парсинг строк с датами
- Сложение/вычитание дней, месяцев
Ошибка №4: Замер времени через SystemTime
Ещё одна классика. Если вам нужно замерить, сколько выполняется функция — никогда не используйте SystemTime. Системное время может быть скорректировано (через NTP или вручную), и ваш замер улетит в минус или покажет нереальные цифры.
// ❌ ПЛОХОЙ ПРИМЕР: замер через SystemTime
let start = SystemTime::now();
// ... какой-то код ...
let end = SystemTime::now();
let duration = end.duration_since(start).unwrap(); // Может упасть!
Правильный подход:
// ✅ ИСПОЛЬЗУЙТЕ Instant для замеров
use std::time::Instant;
let start = Instant::now();
// ... ваш код ...
let duration = start.elapsed();
println!("Код выполнялся: {:?}", duration);
// duration.as_millis() — получите в миллисекундах
Instant гарантирует монотонность: время всегда идёт только вперёд. Это идеальный инструмент для бенчмарков и профилирования.
Ошибка №5: Неправильная обработка ошибок
Как мы уже видели, SystemTime::now().duration_since(UNIX_EPOCH) возвращает Result. Многие новички просто пишут .unwrap() и забывают про это. Но что если системное время сбито? В продакшене такой код молча упадёт.
Правильная обработка:
use std::time::{SystemTime, UNIX_EPOCH};
fn get_timestamp_ms() -> Result<u128, String> {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis())
.map_err(|e| format!("Ошибка времени: {}", e))
}
// Использование
match get_timestamp_ms() {
Ok(ms) => println!("Время: {} мс", ms),
Err(e) => eprintln!("Ошибка: {}", e),
}
Для chrono таких проблем нет — Utc::now().timestamp_millis() всегда возвращает значение без ошибок.
Бонус: Сравнение производительности
Если вы пиш