带有库和二进制文件的 Rust 包?
Posted
技术标签:
【中文标题】带有库和二进制文件的 Rust 包?【英文标题】:Package with both a library and a binary? 【发布时间】:2015-01-12 19:51:42 【问题描述】:我想制作一个 Rust 包,其中包含一个可重用的库(大部分程序都在其中实现)和一个使用它的可执行文件。
假设我没有混淆 Rust 模块系统中的任何语义,我的 Cargo.toml
文件应该是什么样的?
【问题讨论】:
【参考方案1】: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();
【讨论】:
谢谢道格,我会试试的!那么 #![crate_name= ] 和 #![crate_type] 注释是可选的吗? 使用 Cargo 时,这些选项是不必要的,因为 Cargo 将它们作为编译器标志传递。如果您运行cargo build --verbose
,您将在rustc
命令行中看到它们。
你知道为什么[[bin]]
是一个表数组吗?为什么使用[[bin]]
而不是[bin]
?似乎没有这方面的任何文档。
@CMCDragonkai 是 toml 格式规范 [[x]] 是一个反序列化后的数组; IE。单个 crate 可能会生成多个二进制文件,但只能生成一个库(因此是 [lib],而不是 [[lib]])。您可以有多个 bin 部分。 (我同意,这看起来很奇怪,但 toml 一直是一个有争议的选择)。
当我想要的只是库时,有没有办法阻止它编译二进制文件?该二进制文件具有我通过称为“二进制”的功能添加的附加依赖项,当我尝试在没有该功能的情况下编译它时,它无法构建。它抱怨找不到 bin.rs 尝试导入的 crates。【参考方案2】:
简单
创建一个src/main.rs
,它将用作事实上的可执行文件。您无需修改您的Cargo.toml
,此文件将被编译为与库同名的二进制文件。
项目内容:
% tree
.
├── Cargo.toml
└── src
├── lib.rs
└── main.rs
Cargo.toml
[package]
name = "example"
version = "0.1.0"
edition = "2018"
src/lib.rs
use std::error::Error;
pub fn really_complicated_code(a: u8, b: u8) -> Result<u8, Box<dyn Error>>
Ok(a + b)
src/main.rs
fn main()
println!(
"I'm using the library: :?",
example::really_complicated_code(1, 2)
);
并执行它:
% cargo run -q
I'm using the library: Ok(3)
灵活
如果您希望控制二进制文件的名称或拥有多个二进制文件,您可以在src/bin
中创建多个二进制源文件,在src
中创建其余的库源文件。您可以在my project 中查看示例。你根本不需要修改你的Cargo.toml
,src/bin
中的每个源文件都会被编译成同名的二进制文件。
项目内容:
% tree
.
├── Cargo.toml
└── src
├── bin
│ └── mybin.rs
└── lib.rs
Cargo.toml
[package]
name = "example"
version = "0.1.0"
edition = "2018"
src/lib.rs
use std::error::Error;
pub fn really_complicated_code(a: u8, b: u8) -> Result<u8, Box<dyn Error>>
Ok(a + b)
src/bin/mybin.rs
fn main()
println!(
"I'm using the library: :?",
example::really_complicated_code(1, 2)
);
并执行它:
% cargo run --bin mybin -q
I'm using the library: Ok(3)
另见:
How can I specify which crate `cargo run` runs by default in the root of a Cargo workspace?【讨论】:
非常适合 rust 的约定优于配置的方法!两个答案一起,您将获得极大的便利性和灵活性。extern crate example;
从 rust 2018 开始不需要,你可以直接写use example::really_complicated_code;
并使用函数而不用命名范围【参考方案3】:
您可以将lib.rs
和main.rs
放在源文件夹中。 没有冲突,cargo 会构建这两个东西。
要解决文档冲突,请添加到您的Cargo.toml
:
[[bin]]
name = "main"
doc = false
【讨论】:
这将被“另外,您可以创建一个 src/main.rs 用作事实上的可执行文件”。在另一个答案中,不是吗?并且文档冲突由接受的答案解决,对吗?您可能需要澄清您的答案,以说明为什么这是独一无二的。可以参考其他答案来构建它们。【参考方案4】:另一种解决方案是不要试图将两种东西都塞进一个包中。对于具有友好可执行文件的稍微大一点的项目,我发现使用workspace 非常好。
在这里,我创建了一个二进制项目,其中包含一个库,但组织代码的方法有很多:
% tree the-binary
the-binary
├── Cargo.toml
├── src
│ └── main.rs
└── the-library
├── Cargo.toml
└── src
└── lib.rs
Cargo.toml
这使用[workspace]
键并且依赖于库:
[package]
name = "the-binary"
version = "0.1.0"
edition = "2018"
[workspace]
[dependencies]
the-library = path = "the-library"
src/main.rs
fn main()
println!(
"I'm using the library: :?",
the_library::really_complicated_code(1, 2)
);
the-library/Cargo.toml
[package]
name = "the-library"
version = "0.1.0"
edition = "2018"
the-library/src/lib.rs
use std::error::Error;
pub fn really_complicated_code(a: u8, b: u8) -> Result<u8, Box<dyn Error>>
Ok(a + b)
并执行它:
% cargo run -q
I'm using the library: Ok(3)
这个方案有两大好处:
二进制文件现在可以使用仅适用于它的依赖项。例如,您可以包含许多 crate 来改善用户体验,例如命令行解析器或终端格式化。这些都不会“感染”库。
工作区可防止每个组件的冗余构建。如果我们在the-library
和the-binary
目录中都运行cargo build
,则不会两次都构建库——它在两个项目之间共享。
【讨论】:
这似乎是一个更好的方法。显然,这个问题被问到已经有好几年了,但人们仍然在为组织大型项目而苦苦挣扎。与上面选择的答案相比,使用工作区是否有缺点? @Jspies 我能想到的最大缺点是有些工具并不完全知道如何处理工作空间。当与具有某种“项目”概念的现有工具交互时,它们有点奇怪。我个人倾向于采用连续的方法:我从main.rs
中的所有内容开始,然后随着它变大将其分解为模块,最后在稍大一点时拆分为 src/bin
,然后在我开始时转移到工作区大量重用核心逻辑。
谢谢,我会试一试。我当前的项目有几个库,它们是作为项目的一部分开发的,但也可以在外部使用。
它构建并运行良好,但cargo test
似乎忽略了 lib.rs 中的单元测试
@Stein 我想你想要cargo test --all
以上是关于带有库和二进制文件的 Rust 包?的主要内容,如果未能解决你的问题,请参考以下文章
使用 rust 二进制文件运行 docker 映像时出现权限被拒绝错误