Avatar ·

Как в Rust создать пакет, который содержит и библиотеку (lib), и исполняемый файл (bin)?

Всем привет! Я только начал изучать Rust и пытаюсь разобраться с организацией проектов. У меня есть задача: я хочу написать небольшую утилиту для работы с CSV-файлами. Но при этом я хочу, чтобы основную логику (парсинг, фильтрацию) можно было переиспользовать в других проектах как библиотеку. Сейчас мой проект выглядит так: ``` my_project/ ├── Cargo.toml ├── src/ │ └── main.rs ``` В `main.rs` я написал и парсинг, и вывод результатов. Это неудобно: если я захочу использовать тот же парсер в другом проекте, придется копировать код. Я читал, что в Rust можно сделать пакет, который содержит и `lib.rs` (библиотека), и `main.rs` (бинарник). Но я не понимаю, как это правильно настроить. В `Cargo.toml` я не вижу явных инструкций. Как Cargo узнает, что у меня два целевых файла? Можно ли из `main.rs` вызывать функции из `lib.rs`? И как правильно организовать структуру папок? Нужно ли указывать что-то в `Cargo.toml`? Пожалуйста, подскажите, как это сделать правильно. Пример кода и структуры проекта очень помогут. <<>>

Структура пакета с библиотекой и бинарником

В Rust и Cargo нет ничего сложного. По умолчанию, если в папке src/ есть файл lib.rs, Cargo считает, что это библиотека. Если есть main.rs — это бинарник. Если есть оба файла — Cargo автоматически собирает и то, и другое. Никаких специальных настроек в Cargo.toml для этого не требуется (кроме случаев, когда нужно несколько бинарников).

Пример минимального проекта

Создайте следующую структуру:

my_project/
├── Cargo.toml
└── src/
    ├── lib.rs    # библиотека
    └── main.rs   # исполняемый файл

Cargo.toml — самый простой:

[package]
name = "my_project"
version = "0.1.0"
edition = "2021"

Код библиотеки (src/lib.rs)

/// Публичная функция для обработки CSV (пример)
pub fn process_csv(data: &str) -> Vec<Vec<String>> {
    data.lines()
        .filter(|line| !line.trim().is_empty())
        .map(|line| line.split(',').map(|s| s.trim().to_string()).collect())
        .collect()
}

// Приватная функция, недоступная из main.rs
fn _helper() {
    println!("Внутренний помощник библиотеки");
}

Код бинарника (src/main.rs)

// Подключаем библиотеку из того же пакета
// Имя пакета (my_project) становится именем крейта
use my_project::process_csv;

fn main() {
    let csv_data = "\
name,age,city
Alice,30,New York
Bob,25,London";

    let result = process_csv(csv_data);
    for row in &result {
        println!("{:?}", row);
    }
}

Как это работает

  • Библиотека (lib.rs) — это отдельный крейт с именем вашего пакета (в примере my_project). Все, что помечено pub, доступно снаружи.
  • Бинарник (main.rs) — это тоже отдельный крейт, но он неявно считается "зависимым" от библиотеки. Поэтому вы можете использовать use my_project::....
  • Cargo собирает два артефакта: target/debug/my_project (бинарник) и target/debug/libmy_project.rlib (библиотека).

Проверка сборки

Запустите в терминале:

cargo build   # Соберет и библиотеку, и бинарник
cargo run     # Запустит бинарник

Вы увидите результат обработки CSV. При этом библиотеку можно подключить в другой проект, добавив в его Cargo.toml:

[dependencies]
my_project = { path = "../my_project" }  # или версию с crates.io

Дополнительные возможности

Если вам нужно несколько бинарников (например, main.rs и tool.rs), укажите их в Cargo.toml:

[[bin]]
name = "main_tool"
path = "src/bin/main_tool.rs"

[[bin]]
name = "helper"
path = "src/bin/helper.rs"

Но для одного бинарника + библиотеки это не требуется.

<<>>

Альтернативный подход: разделение на workspace

Если вы планируете, что библиотека и бинарник будут развиваться независимо, или если у вас несколько бинарников, использующих одну библиотеку, лучше использовать workspace (рабочую область). Это более "взрослый" подход.

Структура будет такой:

my_project/
├── Cargo.toml          # workspace-файл
├── lib_core/
│   ├── Cargo.toml
│   └── src/
│       └── lib.rs      # библиотека
└── bin_tool/
    ├── Cargo.toml
    └── src/
        └── main.rs     # бинарник

Корневой Cargo.toml (workspace):

[workspace]
members = ["lib_core", "bin_tool"]

В bin_tool/Cargo.toml указываете зависимость:

[dependencies]
lib_core = { path = "../lib_core" }

Плюсы: четкое разделение ответственности, возможность публиковать библиотеку отдельно на crates.io, независимые версии. Минусы: чуть больше файлов и настроек.

Для простого проекта (один бинарник + библиотека) первый ответ (lib.rs + main.rs в одной папке) — идеальный выбор.