Prost для Rust: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое 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(())
}

Советы и типичные ошибки

Советы:

  1. Используйте bytes::Bytes для больших данных — это позволяет избежать лишнего копирования памяти.
  2. Всегда проверяйте версию protoc — Prost требует protoc 3.x или выше.
  3. Используйте cargo build после изменения .proto файлов — build.rs запускается автоматически.
  4. Для вложенных сообщений используйте

Рекомендаций