Let's start from the beginning. Look at the Package Layout chapter in The Cargo Book. As you can see, your package can contain lot of stuff:
- a binary (something you can run) or multiple binaries,
- a single library (shared code),
- example(s),
- benchmark(s),
- integration tests.
Package layout
Not all of the possibilities are listed here, just the binary / library combinations.
A binary
This is an example of a package with single binary. Entry point is the main
function in the src/main.rs
.
Cargo.toml
:
[package]
name = "hallo"
version = "0.1.0"
edition = "2018"
src/main.rs
:
fn main() {
println!("Hallo, Rust here!")
}
$ cargo run
Hallo, Rust here!
A library
This is an example of a package with a library. Libraries don't have entry points, you can't run them. They're used for functionality sharing.
Cargo.toml
:
[package]
name = "hallo"
version = "0.1.0"
edition = "2018"
src/lib.rs
:
pub fn foo() {
println!("Hallo, Rust library here!")
}
$ cargo run
error: a bin target must be available for `cargo run`
Do you see anything in the Cargo.toml
file about a binary or a library? No. The reason is that I've followed the Package Layout and the cargo
knows where to look for things.
A binary and a library
This is an example of a package with a binary and a library.
Cargo.toml
:
[package]
name = "hallo"
version = "0.1.0"
edition = "2018"
src/lib.rs
:
pub const GREETING: &'static str = "Hallo, Rust library here!";
src/main.rs
:
use hallo::GREETING;
fn main() {
println!("{}", GREETING);
}
Same question, do you see anything in the Cargo.toml
file about a binary or a library? No.
This package contains two things:
- a binary (root
src/main.rs
, entry point src/main.rs::main
),
- a library (root
src/lib.rs
, shared code).
A library can be referenced from the binary via use hallo::...
where the hallo
is this package name (Cargo.toml
-> [package]
-> name
).
Your problem
Cargo.toml
:
[package]
name = "hallo"
version = "0.1.0"
edition = "2018"
Same package layout
A library part
src/lib.rs
:
pub mod bar;
pub mod foo;
src/foo.rs
:
pub fn say_foo() {
println!("Foo");
}
src/bar.rs
:
use crate::foo;
pub fn bar() {
foo::say_foo();
}
crate
refers to src/lib.rs
, because we're in the context of our library here.
Treat it as a standalone unit and refer to it via use hallo::...;
from the outside world.
A binary part
src/main.rs
:
use hallo::bar::bar;
fn main() {
bar();
}
Here we're just using our library.
Without a library
Same code, but lib.rs
was renamed to utils.rs
and (foo|bar).rs
files were moved to the src/utils/
folder.
src/utils.rs
:
pub mod bar;
pub mod foo;
src/utils/foo.rs
:
pub fn say_foo() {
println!("Foo");
}
src/utils/bar.rs
:
use super::foo;
// or use crate::utils::foo;
pub fn bar() {
foo::say_foo();
}
We can use crate
here as well, but because we're in the context of our binary, the path differs.
src/main.rs
:
use utils::bar::bar;
mod utils;
fn main() {
bar();
}
Here we just declared another module (utils
) and we're using it.
Summary
Cargo.toml
content:
[package]
name = "hallo"
version = "0.1.0"
edition = "2018"
If there's a src/main.rs
file, you're basically saying this:
[package]
name = "hallo"
version = "0.1.0"
edition = "2018"
[[bin]]
name = "hallo"
src = "src/main.rs"
If there's a src/lib.rs
file, you're basically saying this:
[package]
name = "hallo"
version = "0.1.0"
edition = "2018"
[lib]
name = "hallo"
path = "src/lib.rs"
If there're both of them, you're basically saying this:
[package]
name = "hallo"
version = "0.1.0"
edition = "2018"
[[bin]]
name = "hallo"
path = "src/main.rs"
[lib]
name = "hallo"
path = "src/lib.rs"
Documentation