Пакет Rust с библиотекой и двоичным файлом?

190

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

Если я не перепутал семантику в модульной системе Rust, как должен Cargo.tomlвыглядеть мой файл?

Эндрю Вагнер
источник

Ответы:

206
Tok:tmp doug$ du -a

8   ./Cargo.toml
8   ./src/bin.rs
8   ./src/lib.rs
16  ./src

Cargo.toml:

[package]
name = "mything"
version = "0.0.1"
authors = ["me <me@gmail.com>"]

[lib]
name = "mylib"
path = "src/lib.rs"

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

SRC / lib.rs:

pub fn test() {
    println!("Test");
}

SRC / bin.rs:

extern crate mylib; // not needed since Rust edition 2018

use mylib::test;

pub fn main() {
    test();
}
Doug
источник
2
Спасибо, Даг, я попробую! Являются ли аннотации #! [Crate_name =] и #! [Crate_type] необязательными?
Эндрю Вагнер
4
Когда вы используете Cargo, эти опции не нужны, потому что Cargo передает их как флаги компилятора. Если вы запустите cargo build --verbose, вы увидите их в rustcкомандной строке.
Владимир Матвеев
33
Знаете ли вы, почему [[bin]]массив таблиц? Почему использовать, [[bin]]а не [bin]? Там, кажется, нет никакой документации по этому вопросу.
CMCDragonkai
40
@CMCDragonkai Это спецификация формата toml [[x]] - когда-то десериализованный массив; то есть. один ящик может создать несколько двоичных файлов, но только одну библиотеку (таким образом, [lib], а не [[lib]]). Вы можете иметь несколько секций бен. (Согласен, это выглядит странно, но Томль всегда был спорным выбором).
Дуг
1
Есть ли способ предотвратить компиляцию двоичного файла, когда все, что я хочу, это библиотека? Бинарный файл имеет дополнительные зависимости, которые я добавляю через функцию, называемую «бинарная», когда я пытаюсь скомпилировать ее без этой функции, она не может быть собрана. Он жалуется, что не может найти ящики, которые bin.rs пытается импортировать.
Person93
151

Вы также можете просто добавить двоичные источники src/binи остальные источники src. Вы можете увидеть пример в моем проекте . Вам не нужно ничего менять Cargo.toml, и каждый исходный файл будет скомпилирован в двоичный файл с тем же именем.

Конфигурация другого ответа затем заменяется на:

$ tree
.
├── Cargo.toml
└── src
    ├── bin
    │   └── mybin.rs
    └── lib.rs

Cargo.toml

[package]
name = "example"
version = "0.0.1"
authors = ["An Devloper <an.devloper@example.com>"]

SRC / lib.rs

use std::error::Error;

pub fn really_complicated_code(a: u8, b: u8) -> Result<u8, Box<Error>> {
    Ok(a + b)
}

SRC / bin / mybin.rs

extern crate example; // Optional in Rust 2018

fn main() {
    println!("I'm using the library: {:?}", example::really_complicated_code(1, 2));
}

И выполнить это:

$ cargo run --bin mybin
I'm using the library: Ok(3)

Кроме того, вы можете просто создать объект, src/main.rsкоторый будет использоваться в качестве исполняемого файла defacto. К сожалению, это конфликтует с cargo docкомандой:

Невозможно задокументировать пакет, в котором библиотека и двоичный файл имеют одинаковые имена. Подумайте о переименовании или пометке цели какdoc = false

Shepmaster
источник
13
хорошо сочетается с традиционным подходом ржавчины к конфигурации! оба ответа вместе, и у вас есть большое удобство и гибкость.
летающие овцы
9
extern crate example;не требуется с ржавчины 2018 года, вы можете напрямую написать use example::really_complicated_code;и использовать функцию, не называя области действия
sassman
47

Альтернативное решение - на самом деле не пытаться собрать обе вещи в один пакет. Для проектов чуть большего размера с дружественным исполняемым файлом я нахожу очень полезным использовать рабочее пространство

Мы создаем бинарный проект, который включает в себя библиотеку:

the-binary
├── Cargo.lock
├── Cargo.toml
├── mylibrary
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
└── src
    └── main.rs

Cargo.toml

Это использует [workspace]ключ и зависит от библиотеки:

[package]
name = "the-binary"
version = "0.1.0"
authors = ["An Devloper <an.devloper@example.com>"]

[workspace]

[dependencies]
mylibrary = { path = "mylibrary" }

SRC / main.rs

extern crate mylibrary;

fn main() {
    println!("I'm using the library: {:?}", mylibrary::really_complicated_code(1, 2));
}

MyLibrary / SRC / lib.rs

use std::error::Error;

pub fn really_complicated_code(a: u8, b: u8) -> Result<u8, Box<Error>> {
    Ok(a + b)
}

И выполнить это:

$ cargo run
   Compiling mylibrary v0.1.0 (file:///private/tmp/the-binary/mylibrary)
   Compiling the-binary v0.1.0 (file:///private/tmp/the-binary)
    Finished dev [unoptimized + debuginfo] target(s) in 0.73 secs
     Running `target/debug/the-binary`
I'm using the library: Ok(3)

У этой схемы есть два больших преимущества:

  1. Двоичный файл теперь может использовать зависимости, которые применяются только к нему. Например, вы можете включить множество ящиков для улучшения взаимодействия с пользователем, таких как анализаторы командной строки или форматирование терминала. Ни один из них не «заразит» библиотеку.

  2. Рабочая область предотвращает избыточные сборки каждого компонента. Если мы запустим cargo buildкак каталог, так mylibraryи the-binaryкаталог, библиотека не будет собрана оба раза - она ​​используется для обоих проектов.

Shepmaster
источник
Похоже, это гораздо лучший путь. Очевидно, прошли годы с тех пор, как вопрос был задан, но люди все еще борются с организацией крупных проектов. Есть ли недостаток в использовании рабочей области по сравнению с выбранным ответом выше?
Jspies
4
@Jspies Самый большой недостаток, который я могу вспомнить, это то, что есть некоторые инструменты, которые не совсем знают, как работать с рабочими пространствами. Они оказываются в каком-то странном месте при взаимодействии с существующими инструментами, которые имеют своего рода концепцию «проекта». Лично я склонен использовать континуальный подход: я начинаю со всего main.rs, а затем разбиваю его на модули по мере того, как он становится больше, в конце концов разделяется на то, src/binкогда он становится немного больше, затем перехожу к рабочему пространству, когда начинаю интенсивно использовать основную логику.
Shepmaster
спасибо, я сделаю это. В моем текущем проекте есть несколько библиотек, которые были разработаны как часть проекта, но также используются извне.
Jspies
Он собирается и работает нормально, но, cargo testпохоже, игнорирует модульные тесты в lib.rs
Stein
3
@ Штейн, я думаю, ты хочешьcargo test --all
Shepmaster
18

Вы можете положить lib.rsи main.rsв папку источников вместе. Там нет конфликта, и груз будет строить обе вещи.

Для разрешения конфликта документации добавьте в свой Cargo.toml:

[[bin]]
name = "main"
doc = false
Колодин
источник
3
Это будет описано в разделе « Кроме того, вы можете просто создать файл src / main.rs, который будет использоваться в качестве исполняемого файла defacto ». в другом ответе нет? И конфликт документации разрешается принятым ответом, верно? Возможно, вам придется уточнить свой ответ, чтобы показать, почему это уникально. Можно ссылаться на другие ответы, чтобы опираться на них.
Shepmaster