Когда пишешь сетевой протокол, очень легко прийти к такой цепочке:

прочитали байты -> скопировали в новую область памяти -> распарсили -> получили структуру

На небольших объёмах это вообще не проблема. Но когда через приложение постоянно проходят тысячи сообщений, лишние аллокации и копирование памяти начинают складываться.

В Rust мне для таких задач нравится tokio_util::codec.

В частности - связка Decoder + BytesMut.

Когда реализуем Decoder, мы получаем &mut BytesMut - тот самый буфер, в который Tokio уже складывает данные из сокета.

И если мы нашли в нём полный frame, нам не обязательно копировать его в новый Vec<u8>.

Можно просто сделать:

src.split_to(len)

В результате начало буфера отделяется в новый BytesMut, а исходный буфер остаётся с непрочитанными данными. Само содержимое при этом не приходится перекладывать из одного массива в другой.

Если нужен immutable-буфер, результат можно затем превратить в Bytes через freeze().

Что мне здесь особенно нравится:

Меньше копирований. Не нужно каждый раз создавать новый буфер и переносить туда payload.

Меньше нагрузки на allocator. Один read buffer можно переиспользовать между вызовами декодера вместо постоянного создания временных объектов.

Парсер становится довольно простым. Проверили, хватает ли данных на frame -> отделили нужный кусок -> распарсили.

В итоге вместо постоянного перемещения данных по памяти большая часть работы сводится к управлению границами уже существующего буфера.

Мне вообще нравится этот момент в Rust: с одной стороны пишешь довольно высокоуровневый код через Decoder и traits, а с другой всё ещё хорошо понимаешь, что происходит с памятью под капотом.

#rust #tokio #backend

Когда пишешь сетевой протокол, очень легко прийти к такой цепочке:
прочитали байты -> скопировали в новую область памяти -> распарсили -> получили структуру
На небольших объёмах это вообще не проблема | Сетка — социальная сеть от hh.ru