带有库和二进制文件的Rust包?


186

我想制作一个Rust包,其中包含一个可重用的库(在其中实现了大多数程序),以及一个使用它的可执行文件。

假设我在Rust模块系统中没有混淆任何语义,那么我的Cargo.toml文件应该是什么样?

Answers:


199
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();
}

2
谢谢道格,我会尝试的!那么#![crate_name =]和#![crate_type]注释是可选的吗?
Andrew Wagner 2014年

4
使用Cargo时,这些选项是不必要的,因为Cargo会将它们作为编译器标志传递。如果运行cargo build --verbose,您将在rustc命令行中看到它们。
弗拉基米尔·马特维耶夫(Fladimir Matveev)2014年

33
您知道为什么要用[[bin]]表格数组吗?为什么使用[[bin]]而不[bin]?似乎没有任何文档。
CMCDragonkai 2015年

40
@CMCDragonkai这是toml格式规范[[x]]是一个反序列化的数组;即。一个板条箱可以生成多个二进制文件,但只能生成一个库(因此[lib],而不是[[lib]])。您可以有多个bin部分。(我同意,这看起来很奇怪,但是toml始终是一个有争议的选择)。
Doug

1
当我只需要lib时,有没有办法防止它编译二进制文件?二进制文件具有其他依赖项,这些依赖项是通过称为“二进制文件”的功能添加的,当我尝试在不具有该功能的情况下进行编译时,它将无法构建。它抱怨找不到bin.rs试图导入的板条箱。
Person93

148

您也可以只将二进制源放入src/bin,其余的源放入src。您可以在我的项目中看到一个示例。您不需要修改您的Cargo.toml,每个源文件都将被编译为同名的二进制文件。

然后将另一个答案的配置替换为:

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

货代

[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将用作事实上的可执行文件。不幸的是,这与cargo doc命令冲突:

无法记录库和二进制文件具有相同名称的软件包。考虑重命名或将目标标记为doc = false


13
非常适合rust的Convention-over-configuration方法!这两个答案都在一起,您将获得极大的便利和灵活性。
飞羊

9
extern crate example;从rust 2018开始就不需要了,您可以直接编写use example::really_complicated_code;和使用该函数而无需命名范围
sassman

46

另一种解决方案是实际上不尝试将这两件事都塞进一个包中。对于具有友好可执行文件的较大项目,我发现使用工作区非常好

我们创建一个二进制项目,其中包含一个库:

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

货代

这使用[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 buildmylibrarythe-binary目录中都运行,那么库将不会同时生成-它在两个项目之间共享。


这似乎是更好的方法。显然,问这个问题已经有好几年了,但是人们仍然在组织大型项目方面遇到困难。与上面选择的答案相比,使用工作空间是否有不利之处?
Jspies

4
我能想到的@Jspies最大的缺点是,有些工具并不完全知道如何处理工作区。当与具有某种“项目”概念的现有工具进行交互时,它们有点奇怪。我个人倾向于采取一种连续的方法:首先将所有内容放入其中main.rs,然后将其分解成更大的模块,最后分解成src/bin稍微大一点的模块,然后在我开始大量重用核心逻辑时转移到工作区。
Shepmaster

谢谢,我会旋转一下。我当前的项目有几个库,这些库是项目的一部分,但也可以在外部使用。
Jspies '18 -10-9

它可以构建并运行良好,但cargo test似乎忽略了lib.rs中的单元测试
Stein,

3
@Stein我想你想要cargo test --all
Shepmaster

18

您可以将lib.rsmain.rs放到source文件夹中。没有冲突,货物将造就这两件事。

要解决文档冲突,请添加到您的Cargo.toml

[[bin]]
name = "main"
doc = false

3
另外,您可以创建一个src / main.rs用作实际可执行文件 ”。在另一个答案中,不是吗?并且文档冲突可以通过接受的答案解决,对吗?您可能需要澄清答案,以说明其独特之处。可以参考其他答案来建立它们。
Shepmaster 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.