Creating a library crate to support a Rust binary crate in the same package

Chris Biscardi
InstructorChris Biscardi
Share this video with your friends

Social Share Links

Send Tweet

In our subcommand match we'll add a new function called write to handle our Write subcommand. We'll pass it the argument title and we'll "import" it from a library called digital_garden.

use digital_garden::write;

fn main() -> Result<()> {
    color_eyre::install()?;

    let opt = Opt::from_args();
    dbg!(&opt);
    match opt.cmd {
        Command::Write { title } => write(title),
    }
}

To create this library, add a new section in Cargo.toml. We can give our library a name and set which file to treat as the entrypoint. src/lib.rs is the default file for a library crate, but we can also be explicit about what we want to happen.

[lib]
name = "digital_garden"
path = "src/lib.rs"

This leaves us with two crates, a binary crate, of which we can have any amount, and a library crate, of which we can only have 0 or 1.

We'll create the src/lib.rs file with the following contents.

mod write;

pub use write::write;

Rust's module system is explicit, so when we write mod write, we are saying that the library digital_garden has a module write, such that the path to the module is digital_garden::write.

In our case digital_garden::write corresponds to a file at src/write.rs.

Secondly, the pub use write::write says that we have a function called write in the module called write and that the function will be exposed from lib publicly, so people who have the digital_garden library installed will be able to use the write function as digital_garden::write.

In JavaScript the closest analogue is exporting a function from another file that exports it.

export { write } from './write.js';

By using use, we're shortening the public path to the write function. A private module "write" with a function "write" also exists at digital_garden::write::write. If you try to use this path, Rust will tell you there is a private module there.

Finally, in src/write.rs, we can write a public function that matches the return type of our main function, since we're using it as the return value from our match function.

We'll accept an Option<String> as an argument as well, since that's what we expect from the cli arguments.

use color_eyre::Result;

pub fn write(_title: Option<String>) -> Result<()> {
    todo!()
}

The purpose of creating this module path is because we know that we're going to set up more modules for each subcommand in the future, and we can control both the placement of our code on disk, as well as the path users will interact with to use our module.

Instructor: [0:01] In our subcommand match, we'll add a new function called write to handle our write subcommand. We'll parse it the argument title, and we'll import it from a library called digital garden. Remember to remove the placeholder that we added last lesson.

[0:19] Digital garden doesn't exist, so we get a use of undeclared crate or module digital garden. To create this library, add a new section in Cargo.toml called lib. The name of the lib will be digital_garden, and the path will be at src/lib.rs.

[0:37] Note the difference between the bin declaration and the lib declaration. Bin is an array, whereas lib is not. That's because we can only have one library crate in our package, while we can have as many binaries as we want. In src, we'll create lib.rs with the following contents, mod write, pub use write::write.

[1:04] Now, because I know what we're going to do later and we're going to add more subcommands in different files, we're going to use more of the Rust module system than we would otherwise have to. We could export a public write function from this file and it would work, but instead, we're going to extend this into another file called write.

[1:28] Rust module system is explicit, so when we write, mod write, we're saying that the library, digital garden, which comes from the lib.rs file, has a module called write, such that the path to the module is digital_garden::write. In our case, digital_garden::write corresponds to a file at src/write.rs. We'll create that file now.

[1:56] Back in lib.rs, pub use write::write says that we have a function called write, in the module called write, that we just defined, and that the function will be exposed publicly. People who have the digital_garden library installed will be able to use the write function from the write module as digital_garden::write.

[2:23] If you're used to JavaScript, the closest analog is, export write, from write.js. By using use, we're effectively shortening the import path to the write function.

[2:37] In write.rs, we'll create a function called write with a visibility of public that accepts the title, that has an option of a string, which is the same argument we parsed in earlier, and returns a result, like all the other results in our application.

[2:55] Inside of this function, we'll leave our to do, as we're not done writing this function yet. We can see that our tests are parsing, which means that our write function is being correctly targeted from our binary crate. To drive the point home a little further, there is a path at digital garden, write:write. That would be lib.rs write:write.

[3:27] Because the module path in Rust is explicit and this module is private, while the function is public inside of the current module, we can't reach into write to arbitrarily use functions. We can only use the function that we've publicized at the digital garden level.