🦀 Error Handling: Библиотеки против Приложений
Часто вижу в код-ревью кашу из способов обработки ошибок. Давайте раз и навсегда зафиксируем базу, чтобы ваш код был идиоматичным.
Есть два лагеря, и вам нужно быть в обоих, но в разное время:
1. Вы пишите Библиотеку?
Используйте
Почему: Вашим пользователям важно
2. Вы пишите Приложение (CLI, Backend)?
Используйте
Почему: Вам чаще всего плевать на тип ошибки в глубине стека, вам важно прокинуть её наверх (
Итог: В библиотеках даем структуру (
#rust #tips #error_handling
👉 @rust_lib
Часто вижу в код-ревью кашу из способов обработки ошибок. Давайте раз и навсегда зафиксируем базу, чтобы ваш код был идиоматичным.
Есть два лагеря, и вам нужно быть в обоих, но в разное время:
1. Вы пишите Библиотеку?
Используйте
thiserror.Почему: Вашим пользователям важно
матчить ошибки. Им нужно знать, что именно пошло не так (NetworkError, ParseError), чтобы программно на это отреагировать. Вы не должны навязывать им тяжелые трейты.
#[derive(thiserror::Error, Debug)]
pub enum MyLibError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Invalid header")]
InvalidHeader,
}
2. Вы пишите Приложение (CLI, Backend)?
Используйте
anyhow.Почему: Вам чаще всего плевать на тип ошибки в глубине стека, вам важно прокинуть её наверх (
main), залоггировать контекст и упасть (или отдать 500-ку).
use anyhow::{Context, Result};
fn main() -> Result<()> {
let config = std::fs::read_to_string("config.toml")
.context("Не удалось прочитать конфиг")?;
Ok(())
}
Итог: В библиотеках даем структуру (
thiserror), в приложениях собираем контекст (anyhow). Не смешивайте.#rust #tips #error_handling
👉 @rust_lib
👍22👎1🥰1💩1
🤠 Трюк Индианы Джонса:
Типичная ситуация: у вас есть
Компилятор бьет по рукам: нельзя просто так переместить (move) значение из-за мутабельной ссылки. Новички часто делают
Решение:
Оно забирает значение из ссылки, оставляя на его месте
Это работает для всего, что реализует
Это zero-cost, семантически верно и намного быстрее клонирования.
#rust #std #optimization #tips
👉 @rust_lib
std::mem::takeТипичная ситуация: у вас есть
&mut self, и вы хотите забрать поле (например, String или Vec), что-то с ним сделать, и, возможно, вернуть обратно или сохранить измененную версию.Компилятор бьет по рукам: нельзя просто так переместить (move) значение из-за мутабельной ссылки. Новички часто делают
.clone(), чтобы успокоить borrow checker. Но это лишняя аллокация!Решение:
std::mem::take.Оно забирает значение из ссылки, оставляя на его месте
Default::default().
struct Buffer {
data: Vec<u8>,
}
impl Buffer {
fn process(&mut self) {
// Мы "выкрадываем" вектор.
// В self.data теперь пустой Vec (без аллокаций).
let raw_data = std::mem::take(&mut self.data);
// Тяжелая обработка в другом потоке/функции,
// требующая владения (ownership)
let processed = heavy_transform(raw_data);
// Возвращаем результат
self.data = processed;
}
}
Это работает для всего, что реализует
Default (Option, String, Vec). Для типов без Default есть std::mem::replace.Это zero-cost, семантически верно и намного быстрее клонирования.
#rust #std #optimization #tips
👉 @rust_lib
🔥25👍8❤6🥰1😁1🤗1