Что такое Prost и зачем он нужен?
Prost — это мощная и производительная библиотека для языка программирования Rust, предназначенная для работы с Protocol Buffers (protobuf). Protocol Buffers — это разработанный Google механизм сериализации структурированных данных, который является более компактным и быстрым по сравнению с JSON или XML.
Prost решает несколько ключевых задач:
- Генерация кода: Автоматически создает Rust-структуры из
.protoфайлов, избавляя разработчика от рутинной работы. - Сериализация и десериализация: Преобразует данные из бинарного формата protobuf в Rust-структуры и обратно.
- Высокая производительность: Использует нулевое копирование (zero-copy) и эффективное управление памятью, что делает его одним из самых быстрых решений для работы с protobuf в Rust.
- Безопасность: Полностью написан на Rust, что гарантирует безопасность памяти и потоков.
Библиотека особенно полезна в микросервисной архитектуре, системах обмена данными между сервисами, хранении конфигураций и любых сценариях, где требуется эффективная сериализация.
Установка
Prost устанавливается через менеджер пакетов Cargo. Для начала работы добавьте следующие зависимости в ваш Cargo.toml:
[dependencies]
prost = "0.12"
prost-build = "0.12"
tonic = "0.11" # опционально, для gRPC
bytes = "1.5" # для работы с буферами
[build-dependencies]
prost-build = "0.12"
Также вам понадобится установить компилятор protobuf (protoc). На Ubuntu/Debian:
sudo apt install protobuf-compiler
На macOS:
brew install protobuf
На Windows скачайте бинарник с официального репозитория protobuf и добавьте в PATH.
Быстрый старт
Создайте файл person.proto:
syntax = "proto3";
message Person {
string name = 1;
int32 age = 2;
repeated string phone_numbers = 3;
}
Создайте build.rs в корне проекта:
fn main() -> Result<(), Box<dyn std::error::Error>> {
prost_build::compile_protos(&["src/person.proto"], &["src/"])?;
Ok(())
}
Теперь в main.rs:
use prost::Message;
// Сгенерированный код будет в модуле person
pub mod person {
include!(concat!(env!("OUT_DIR"), "/person.rs"));
}
fn main() {
// Создаем объект
let person = person::Person {
name: "Алексей".to_string(),
age: 30,
phone_numbers: vec!["+7-999-123-45-67".to_string()],
};
// Сериализуем в байты
let mut buf = Vec::new();
person.encode(&mut buf).unwrap();
println!("Сериализовано {} байт", buf.len());
// Десериализуем обратно
let decoded = person::Person::decode(&buf[..]).unwrap();
println!("Имя: {}, Возраст: {}", decoded.name, decoded.age);
}
Основные методы и функции
1. Message::encode
Что делает: Сериализует сообщение в бинарный формат protobuf и записывает в буфер.
Параметры:
&self— ссылка на сообщениеbuf: &mut impl BufMut— буфер для записи (обычноVec<u8>)
Пример:
let mut buf = Vec::new();
my_message.encode(&mut buf).unwrap();
2. Message::decode
Что делает: Десериализует байты обратно в структуру Rust.
Параметры:
buf: impl Buf— источник байтов (срез, Vec, Bytes)
Пример:
let decoded = MyMessage::decode(&buf[..]).unwrap();
3. Message::encoded_len
Что делает: Возвращает длину сообщения в байтах после сериализации без фактической сериализации.
Параметры:
&self— ссылка на сообщение
Пример:
let len = my_message.encoded_len();
println!("Сообщение займет {} байт", len);
4. Message::encode_length_delimited
Что делает: Сериализует сообщение с префиксом длины (varint). Полезно для потоковой передачи.
Параметры:
&self— ссылка на сообщениеbuf: &mut impl BufMut— буфер для записи
Пример:
let mut buf = Vec::new();
my_message.encode_length_delimited(&mut buf).unwrap();
5. Message::decode_length_delimited
Что делает: Десериализует сообщение с префиксом длины.
Параметры:
buf: impl Buf— источник байтов
Пример:
let decoded = MyMessage::decode_length_delimited(&buf[..]).unwrap();
6. prost::bytes::Bytes
Что делает: Создает неизменяемый буфер байтов с нулевым копированием для эффективной передачи данных.
Параметры:
data: &[u8]— исходные данные
Пример:
use bytes::Bytes;
let data = Bytes::from(&buf[..]);
let decoded = MyMessage::decode(data).unwrap();
7. prost_build::compile_protos
Что делает: Компилирует .proto файлы в Rust код во время сборки.
Параметры:
protos: &[PathBuf]— пути к .proto файламincludes: &[PathBuf]— пути для поиска импортов
Пример (build.rs):
prost_build::compile_protos(
&["src/messages.proto"],
&["src/"]
).unwrap();
8. prost::encoding::decode_varint
Что делает: Декодирует varint (переменную длину) из буфера. Используется для ручного разбора protobuf.
Параметры:
buf: &mut impl Buf— буфер для чтения
Пример:
use prost::encoding::decode_varint;
let mut buf = &bytes[..];
let value = decode_varint(&mut buf).unwrap();
9. prost::encoding::encode_varint
Что делает: Кодирует число в формат varint.
Параметры:
value: u64— число для кодированияbuf: &mut impl BufMut— буфер для записи
Пример:
use prost::encoding::encode_varint;
let mut buf = Vec::new();
encode_varint(42, &mut buf);
10. prost::Message::merge
Что делает: Объединяет данные из буфера с существующим сообщением (дополняет поля).
Параметры:
&mut self— изменяемая ссылка на сообщениеbuf: impl Buf— буфер с новыми данными
Пример:
let mut person = Person::default();
person.merge(&additional_data[..]).unwrap();
Реальный практический пример: Система логирования
Создадим систему сбора логов с использованием gRPC и Prost.
log.proto:
syntax = "proto3";
package logger;
service Logger {
rpc SendLog (LogRequest) returns (LogResponse);
}
message LogRequest {
string service_name = 1;
string level = 2;
string message = 3;
int64 timestamp = 4;
}
message LogResponse {
bool success = 1;
string error = 2;
}
build.rs:
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::compile_protos("proto/log.proto")?;
Ok(())
}
server.rs:
use tonic::{transport::Server, Request, Response, Status};
use logger::logger_server::{Logger, LoggerServer};
use logger::{LogRequest, LogResponse};
pub mod logger {
tonic::include_proto!("logger");
}
#[derive(Default)]
pub struct LoggerService;
#[tonic::async_trait]
impl Logger for LoggerService {
async fn send_log(
&self,
request: Request<LogRequest>,
) -> Result<Response<LogResponse>, Status> {
let log = request.into_inner();
println!(
"[{}] {}: {}",
log.level, log.service_name, log.message
);
Ok(Response::new(LogResponse {
success: true,
error: String::new(),
}))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50051".parse()?;
let logger = LoggerService::default();
Server::builder()
.add_service(LoggerServer::new(logger))
.serve(addr)
.await?;
Ok(())
}
client.rs:
use logger::logger_client::LoggerClient;
use logger::LogRequest;
pub mod logger {
tonic::include_proto!("logger");
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = LoggerClient::connect("http://[::1]:50051").await?;
let request = tonic::Request::new(LogRequest {
service_name: "auth-service".to_string(),
level: "INFO".to_string(),
message: "User logged in successfully".to_string(),
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64,
});
let response = client.send_log(request).await?;
println!("Response: {:?}", response.into_inner());
Ok(())
}
Советы и типичные ошибки
Советы:
- Используйте
bytes::Bytesдля больших данных — это позволяет избежать лишнего копирования памяти. - Всегда проверяйте версию protoc — Prost требует protoc 3.x или выше.
- Используйте
cargo buildпосле изменения .proto файлов — build.rs запускается автоматически. - Для вложенных сообщений используйте
Также в библиотеке
Конфигурация приложений Swift: инструменты и библиотеки для iOS/macOS
Валидация данных в C++: библиотеки, методы и примеры кода
Java для геолокации и карт: библиотеки, API и примеры
Валидация данных в TypeScript: Zod, Yup и другие инструменты