Avatar ·

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

Всем привет! Я только начал изучать Rust и пытаюсь организовать свой проект. У меня есть некоторая логика, которую я хочу переиспользовать: например, функции для работы с числами (факториал, числа Фибоначчи). Я хочу сделать так, чтобы эти функции были доступны как библиотека (чтобы их можно было подключить в других проектах через `use`), но при этом у меня был бы исполняемый файл (бинарник), который запускается и демонстрирует работу этих функций через консоль. Я создал проект через `cargo new my_project`, и он создал только `src/main.rs`. Я читал, что можно добавить `src/lib.rs`, но как правильно настроить `Cargo.toml`? Нужно ли писать что-то вроде `[lib]` или `[[bin]]`? И как потом запускать именно бинарник, если я хочу его использовать, а библиотеку — подключать в других крейтах? Вот пример того, что я пытаюсь сделать: ```rust // src/lib.rs pub fn factorial(n: u64) -> u64 { (1..=n).product() } ``` ```rust // src/main.rs mod lib; // так можно? fn main() { println!("{}", my_project::factorial(5)); } ``` Но компилятор ругается. Подскажите, как правильно организовать структуру пакета с библиотекой и бинарником в Rust? Заранее спасибо! <<>>

Основное решение: стандартная структура Cargo

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

Ваш код почти правильный, но есть нюансы. Вот корректная структура:

my_project/
├── Cargo.toml
└── src/
    ├── lib.rs    # библиотека
    └── main.rs   # бинарник

Шаг 1: Библиотека (src/lib.rs)

Экспортируйте функции с ключевым словом pub. Они станут частью публичного API библиотеки:

// src/lib.rs
pub fn factorial(n: u64) -> u64 {
    (1..=n).product()
}

pub fn fibonacci(n: u32) -> u64 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

Шаг 2: Бинарник (src/main.rs)

В бинарнике вы можете обращаться к библиотеке как к внешнему крейту с тем же именем, что и пакет (по умолчанию — имя проекта). Не используйте mod lib; — это пытается объявить модуль, а не подключить библиотеку. Правильно — использовать use:

// src/main.rs
// Имя крейта совпадает с именем пакета в Cargo.toml
use my_project::{factorial, fibonacci};

fn main() {
    println!("Факториал 5: {}", factorial(5));
    println!("Фибоначчи 10: {}", fibonacci(10));
}

Шаг 3: Cargo.toml

В простейшем случае ничего дописывать не нужно. Cargo сам определит:

  • Библиотеку — по src/lib.rs (имя крейта = имя пакета)
  • Бинарник — по src/main.rs (имя бинарника = имя пакета)

Если хотите явно указать имена или добавить несколько бинарников, используйте секции [lib] и [[bin]]:

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

[lib]
name = "my_lib"  # имя библиотеки (по умолчанию = имя пакета)
path = "src/lib.rs"

[[bin]]
name = "my_bin"  # имя исполняемого файла
path = "src/main.rs"

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

  • Библиотека компилируется в .rlib файл. Её можно подключить в других проектах через [dependencies] в их Cargo.toml.
  • Бинарник компилируется в исполняемый файл. Он может использовать библиотеку этого же пакета как внешний крейт.
  • При запуске cargo run выполняется главный бинарник. Если бинарников несколько, используйте cargo run --bin my_bin.

Проверка

Выполните в терминале:

cargo build    # скомпилирует и библиотеку, и бинарник
cargo run      # запустит main.rs
cargo test     # протестирует библиотеку (если есть тесты в lib.rs)

Важные замечания

  • Библиотека не может иметь точку входа fn main(). Она только экспортирует функции, типы и модули.
  • Бинарник не может быть подключен как зависимость в других проектах.
  • Если вы хотите, чтобы бинарник использовал внутренние (непубличные) функции библиотеки, они должны быть помечены как pub(crate) или просто pub.
<<>>

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

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

Структура проекта:

my_project/
├── Cargo.toml          # корневой workspace
├── my_lib/
│   ├── Cargo.toml      # библиотека
│   └── src/lib.rs
└── my_bin/
    ├── Cargo.toml      # бинарник
    └── src/main.rs

Корневой Cargo.toml:

[workspace]
members = ["my_lib", "my_bin"]

В my_bin/Cargo.toml добавляем зависимость от библиотеки:

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

Теперь бинарник может использовать библиотеку через use my_lib::factorial;. Преимущество: библиотека и бинарник компилируются отдельно, у каждого свои версии и зависимости. Это удобно для крупных проектов или если вы планируете публиковать библиотеку на crates.io.