问题:
Rust package with both a library and a binary?
Rust包如何既有一个库又有一个可执行文件?
I would like to make a Rust package that contains both a reusable library (where most of the program is implemented), and also an executable that uses it.
我想创建一个Rust包,它既包含一个可重复使用的库,程序的大部分地方是它来实现的,同时也包含一个可执行的文件来使用这个库。
Assuming I have not confused any semantics in the Rust module system, what should my Cargo.toml file look like?
假设我没有在Rust系统模块中混淆任何语义,我的Cargo.toml文件看起来应该是什么样子的?
答案1(selected)
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();
}
Thanks Doug, I will try it! Are the #![crate_name= ] and #![crate_type] annotations optional then?
感谢Doug,我会试一下!#![crate_name= ] 和 #![crate_type] 这两个注释是可选的么?
When you use Cargo, these options are unnecessary because Cargo passes them as compiler flags. If you run cargo build --verbose, you'll see them in rustc command line.
当你使用Cargo时,这些选项都是不必要的,因为Cargo作为编译器标记传递他们。如果你运行 cargo build --verbose,你会在rustc命令行看到他们。
Do you know why [[bin]] is an array of tables? Why use [[bin]] and not [bin]? There doesn't seem to be any documentation on this.
你知道为什么[[bin]]是一个数组么?为什么使用 [[bin]]而不是[bin]?似乎没有任何关于这个的文档。
@CMCDragonkai It's the toml format specification [[x]] is an array once deserialized; ie. a single crate may produce multiple binaries, but only one library (thus [lib], not [[lib]]). You can have multiple bin sections. (I agree, this looks weird, but toml was always a controversial choice)
@CMCDragonkai 这是toml格式规范。 [[x]]是反序列化后的数组。比如,一个单独的crate可能产生多个可执行文件,但是仅仅有一个库(那么是[lib]不是[[lib]])。你能够有多个bin的部分。(我同意,这个看起来有些奇怪,但是toml总是一个有争议的选择)
Is there a way to prevent it from compiling the binary when all I want is the lib? The binary has additional dependencies which I add through a feature called "binary", when I try to compile it without that feature, it fails to build. It complains that it can't find the crates that bin.rs is trying to import.
当我只想是lib,有没有办法阻止他编译为可执行文件?这个可执行文件有额外的依赖,我通过叫做"binary"特性添加的。当我尝试没有那个特性进行编译,构建就会失败。它抱怨找不到bin.rs试图导入的那个crate。
答案2
You can also just put binary sources in src/bin and the rest of your sources in src. You can see an example in my project. You do not need to modify your Cargo.toml at all, and each source file will be compiled to a binary of the same name.
你也可以将可执行源代码放在src/bin目录中,其余代码放在src目录中。你可以看这个例子 my project。你根本不需要修改你的Cargo.toml,并且每个源文件都会被编译到一个同名的二进制文件中。
The other answer’s configuration is then replaced by:
其他回答的配置文件替换为:
$ 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));
}
And execute it:
$ cargo run --bin mybin
I'm using the library: Ok(3)
Additionally, you can just create a src/main.rs that will be used as the defacto executable. Unfortunately, this conflicts with the cargo doc command:
另外,你可以只创建一个src/main.rs,将作为实际上可执行文件。不幸的是,这个会和cargo doc 命令冲突。
Cannot document a package where a library and a binary have the same name. Consider renaming one or marking the target as doc = false
当库和可执行文件有相同名字时,不能记录为一个包。考虑重命名或者标记目标为doc = false
fits well with rust’s convention-over-configuration approach! both answers together and you have some great convenience and flexibility.
非常符合rust的约定优于配置的方法!结合两个答案,你会有一些很大方便性和灵活性。
extern crate example; is not required as of rust 2018, you can directly write use example::really_complicated_code; and use the function without naming the scope
extern crate example;不要求rust 2018版本,你能够直接写 use example::really_complicated_code;,使用函数方法时,不需要增加名字范围extern crate example;
答案3
An alternate solution is to not actually try to cram both things into one package. For slightly larger projects with a friendly executable, I've found it very nice to use a workspace
另一个解决方法是不需要尝试将两个东西塞到一个包里。对于稍微大一些的项目,为了使他友好的执行,我发现使用workspace非常好。
We create a binary project that includes a library inside of it:
我们创建一个二进制项目,在其中它包含了一个库:
the-binary
├── Cargo.lock
├── Cargo.toml
├── mylibrary
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
└── src
└── main.rs
Cargo.toml
This uses the [workspace] key and depends on the library:
这里使用[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)
}
And execute it:
执行它:
$ 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)
There are two big benefits to this scheme:
这个模式有两个大优点:
1. The binary can now use dependencies that only apply to it. For example, you can include lots of crates to improve the user experience, such as command line parsers or terminal formatting. None of these will "infect" the library.
1. 现在二进制能够使用这些仅适用于它的依赖项。举个例子,你能够包含许多crate,提升用户的体验,比如命令行解析器或者终端格式化。这些没有一个会”感染“库。
2. The workspace prevents redundant builds of each component. If we run cargo build in both the mylibrary and the-binary directory, the library will not be built both times — it's shared between both projects.
2. 工作空间防止多余的构建每一个部分。如果我们在mylibrary和the-binary目录下运行cargo build,库不会被构建两次—它被两个项目共享。
This seems like a much better way to go. Obviously it's been years since the question was asked but people still struggle with organizing large projects. Is there a downside to using a workspace versus the selected answer above?
这个似乎是一个更好的方法。显然,这个问题已经被提出很多年,但是人们仍然在与组织大型项目斗争。与上面选择的答案,使用工作空间有什么缺点么?
@Jspies the biggest downside I can think of off the top of my head is that there are some tools that don't fully know how to deal with workspaces. They are kind of in a weird spot when interacting with existing tools that have some sort of "project" concept. I personally tend to take a continuum approach: I start with everything in main.rs, then break it up into modules as it gets bigger, finally splitting to src/bin when it's just a little bigger, then moving to a workspace when I start heavily reusing the core logic.
@Jspies 我能直接想到的最大的缺点是一些工具不能完全知道怎么样处理工作空间。当与存在一些项目概念的工具进行交互时,他们有一些奇怪的斑点。我个人倾向采用一个连续的方法:我在main.rs中开始所有的事情。然后在它变得更大时,把他分解成模块,最后当它稍微大一点儿的时候,分解成src/bin,当我开始重新使用核心逻辑,则移动到一个空间中。
thanks I will give it a spin. my current project has a couple of libs that are developed as part of the project but also used externally.
谢谢,我将给它一个spin。我当前的项目有两个库,它们作为项目的一部分被开发,但是使用于外部。
It builds and runs fine, but cargo test seems to ignore unit tests in lib.rs
它创建和运行时可以的,但是cargo test 似乎忽略了lib.rs的单元测试。
@Stein I think you want cargo test --all
@Stein 我想你是想cargo test --all