440

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.

Assuming I have not confused any semantics in the Rust module system, what should my Cargo.toml file look like?

5 Answers 5

422
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 <[email protected]>"]

[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();
}
Sign up to request clarification or add additional context in comments.

11 Comments

Thanks Doug, I will try it! Are the #![crate_name= ] and #![crate_type] annotations optional then?
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.
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.
@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).
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.
|
297

Simple

Create a src/main.rs that will be used as the defacto executable. You do not need to modify your Cargo.toml and this file will be compiled to a binary of the same name as the library.

The project contents:

% 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)
    );
}

And execute it:

% cargo run -q
I'm using the library: Ok(3)

Flexible

If you wish to control the name of the binary or have multiple binaries, you can create multiple binary source files in src/bin and the rest of your library 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 in src/bin will be compiled to a binary of the same name.

The project contents:

% 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)
    );
}

And execute it:

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

See also:

2 Comments

fits well with rust’s convention-over-configuration approach! both answers together and you have some great convenience and flexibility.
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
130

An alternate solution is to not 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.

Here, I create a binary project that includes a library inside of it, but there are many possible ways of organizing the code:

 % tree the-binary
the-binary
├── Cargo.toml
├── src
│   └── main.rs
└── the-library
    ├── Cargo.toml
    └── src
        └── lib.rs

Cargo.toml

This uses the [workspace] key and depends on the library:

[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)
}

And execute it:

% cargo run -q
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.

  2. The workspace prevents redundant builds of each component. If we run cargo build in both the the-library and the-binary directory, the library will not be built both times — it's shared between both projects.

8 Comments

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.
@Stein I think you want cargo test --all
@Shepmaster can you explain why you used [dependencies] instead of members = ["the-library"]
@DylanKerler those control two different things. [dependencies] indicates a reliance on another package in order to compile. members specifies which packages should participate in the workspace. In this case, "An empty [workspace] table can be used with a [package] to conveniently create a workspace with the package and all of its path dependencies".
|
27

You can put lib.rs and main.rs to sources folder together. There is no conflict and cargo will build both things.

To resolve documentaion conflict add to your Cargo.toml:

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

1 Comment

That would be covered by "Additionally, you can just create a src/main.rs that will be used as the defacto executable". in the other answer, no? And the documentation conflict is resolved by the accepted answer, right? You may need to clarify your answer to show why this is unique. It's OK to reference the other answers to build upon them.
1

This is an old question, but I just ran into it myself and can offer a "simpler" answer (for small values of "simpler").

It's not necessary to change the name of the library or the main create executable, nor do you have to put the main crate bin sources in a separate subfolder (which are all suggested by the accepted answer). But you do have to:

  1. change the name of the main bin source file (away from main.rs).
  2. do all the imports in the main bin via external crate name, so
    use lib_with_bin::xxx rather than use crate::xxx
    (imports in the lib sources can remain use crate::xxx)
  3. And (proving there's no free lunch in Rust), you do have to declare the top level modules in the lib as pub mod rather than just mod.

Doing this basically makes your crate binary into an associated binary that uses the library as an external crate, kind of like we do with examples (except that the sources for the executable live in the main /src tree). I think the nit about pub mod arises because I insisted on the main crate binary executable have the same name as the lib. (maybe?)

Here's a project that demonstrates the whole thing: https://github.com/bobhy/lib_with_bin

and it both runs and runs test OK:

-- ~/s/r/lib_with_bin ------------------------------------------------- (main|…) -- 
$ cargo run
   Compiling lib_with_bin v0.1.0 (/home/me/src/rustBud/lib_with_bin)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.20s
     Running `target/debug/lib_with_bin`
crate::add(2, 2): 4, component::comp1(2, 2): 4
-- ~/s/r/lib_with_bin ------------------------------------------------- (main|…) -- 
$ cargo test
   Compiling lib_with_bin v0.1.0 (/home/me/src/rustBud/lib_with_bin)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.24s
     Running unittests src/lib.rs (target/debug/deps/lib_with_bin-f4bb6fef12b77648)

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/lib_with_bin.rs (target/debug/deps/lib_with_bin-f9f700da56acb6f5)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests lib_with_bin

running 0 tests


Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.