Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust package with both a library and a binary?

People also ask

What is a .crate file Rust?

A crate is a compilation unit in Rust. Whenever rustc some_file.rs is called, some_file.rs is treated as the crate file. If some_file.rs has mod declarations in it, then the contents of the module files would be inserted in places where mod declarations in the crate file are found, before running the compiler over it.

Where are Rust binaries stored?

All binaries installed with cargo install are stored in the installation root's bin folder. If you installed Rust using rustup.rs and don't have any custom configurations, this directory will be $HOME/. cargo/bin. Ensure that directory is in your $PATH to be able to run programs you've installed with cargo install .

What is extern crate?

An extern crate declaration specifies a dependency on an external crate. The external crate is then bound into the declaring scope as the identifier provided in the extern crate declaration.


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

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:

  • How can I specify which crate `cargo run` runs by default in the root of a Cargo workspace?

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.


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