TokioMiddleTechnical

Как feature flags Tokio влияют на binary size и доступные модули?

Каждый feature flag добавляет соответствующий код в бинарник (net +120 KB, sync +100 KB и т.д.). full удобен для разработки, но в production используйте минимальный явный набор; cargo-bloat помогает измерить реальный вклад.

Влияние feature flags на binary size и модули

Каждый feature flag Tokio добавляет скомпилированный код конкретного модуля в итоговый бинарник. Rust компилирует только то, что запрошено — но «лишние» флаги могут незаметно раздуть исполняемый файл и замедлить сборку.

Измерение влияния

# Создайте два одинаковых проекта с разными флагами и сравните
cargo build --release
ls -lh target/release/my_app

# Анализ символов
nm --size-sort target/release/my_app | tail -20

# Что занимает место в бинарнике
cargo install cargo-bloat
cargo bloat --release --crates
cargo bloat --release -n 20  # топ-20 функций по размеру

Конкретные примеры влияния флагов

# Вариант A: минимальный
[dependencies]
tokio = { version = "1", features = ["rt", "macros"] }
# Модули: current_thread runtime, #[tokio::main]
# Нет: net, fs, time, sync — их API недоступны

# Вариант B: полный
[dependencies]
tokio = { version = "1", features = ["full"] }
# Добавляет: epoll/kqueue driver, fs syscalls,
# DNS resolver, Unix sockets, signal handling
# Реальное сравнение (stripped release build)
# Только rt + macros:      ~280 KB
# + net:                   +120 KB (epoll/IO driver)
# + fs:                    +80 KB
# + time:                  +60 KB
# + sync:                  +100 KB (channels, mutex)
# full (всё):              ~900 KB

# С LTO и strip:
cargo build --release
strip target/release/my_app

Какие модули становятся доступны

// С features = ["net", "io-util"]
use tokio::net::{TcpListener, TcpStream}; // Ok
use tokio::io::{AsyncReadExt, AsyncWriteExt}; // Ok

// Без feature "time" — не скомпилируется:
// use tokio::time::sleep; // error: module `time` not found

// Без feature "sync":
// use tokio::sync::Mutex; // error
// Но std::sync::Mutex — всегда доступен

Оптимизация binary size

# Cargo.toml — profile.release настройки
[profile.release]
opt-level = "z"    # оптимизация по размеру (vs "3" по скорости)
lto = true         # Link-Time Optimization — убирает мёртвый код
codegen-units = 1  # одна единица компиляции — лучший LTO
strip = true       # убрать символы отладки
panic = "abort"    # убрать unwinding код (~50 KB)

Проверка какие features включены транзитивно

# Посмотреть все активированные features для tokio
cargo metadata --format-version 1 | \
  python3 -c "
import json,sys
d=json.load(sys.stdin)
for p in d['packages']:
    if p['name']=='tokio':
        print(p['features'])
"

# Или через cargo tree
cargo tree -e features | grep tokio

Рекомендации для библиотечных крейтов

[features]
default = []
async-net = ["tokio/net", "tokio/io-util"]
async-fs = ["tokio/fs"]

[dependencies]
tokio = { version = "1", optional = true, default-features = false }

Библиотека не должна принуждать пользователей включать features, которые им не нужны. Объявляйте Tokio как опциональную зависимость и прокидывайте features через собственные флаги.

Подводные камни

  • full в production: features = ["full"] удобен для прототипов, но тянет весь Tokio включая DNS-resolver, Unix-сокеты и signal handler — даже если приложение их не использует.
  • Cargo feature unification: если зависимость A требует tokio/net, а зависимость B требует tokio/time, итоговый бинарник включит оба — минимальный набор в Cargo.toml не защищает от транзитивных включений.
  • Размер vs скорость: opt-level = "z" уменьшает размер, но может замедлить горячие пути; измеряйте перед принятием решения.
  • lto = true замедляет сборку: LTO требует дополнительного прохода линковщика — добавьте только в profile.release, не в profile.dev.
  • Скрытые зависимости через "full": некоторые крейты (например, axum) включают tokio/full транзитивно — используйте cargo tree --features для аудита реального набора.
  • panic = "abort" несовместим с некоторыми C-библиотеками: если FFI-зависимость ожидает размотку стека (unwinding), panic = "abort" приведёт к некорректному завершению.

Common mistakes

  • Отвечать определением без production-сценария.
  • Не называть runtime boundary, security boundary или failure mode.
  • Игнорировать версию API, observability и тестовую проверку.

What the interviewer is testing

  • Объясняет механизм своими словами и без выдуманных API.
  • Называет реальные риски, диагностику и критерий корректности.
  • Связывает ответ с текущей документацией и миграционными ограничениями.

Sources

Related topics