Rust modules confusion when there is main.rs and lib.rs
Solution 1
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 pointsrc/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
Solution 2
In short the official Rust book has this to say:
If a package contains
src/main.rs
andsrc/lib.rs
, it has two crates: a library and a binary, both with the same name as the package.
Furthermore the Rust reference says this:
crate
resolves the path relative to the current crate
So there are actually two crates in your project, and to which crate the crate
qualifier resolves to depends on where you call it.
Now in your code example, if you want things to compile you have to remove mod bar;
from src/main.rs
. Otherwise you'll be declaring that bar
is a module within two crates.
After you remove that, then because in src/lib.rs
you had:
pub mod foo;
pub mod bar;
bar
would now be a module within src/lib.rs
's crate, so the crate
qualifier in bar.rs
would then refer to src/lib.rs
's hello-world
crate, which is what you want.
One more thing, if you wanted to access items that are exposed in src/lib.rs
from src/main.rs
, you have to do as @zrzka said, which is to name the name of the crate that both src/lib.rs
and src/main.rs
share. For example, in your project which is named hello-world
:
use hello_world::foo;
fn main() {
foo::say_foo();
}
is how you import the foo
module declared in src/lib.rs
into src/main.rs
.
However it does appear that the importing behavior doesn't work the other way. I.e. if you declare some public module in src/main.rs
, you can't import it into the src/lib.rs
crate even when you specify the name of the crate. I couldn't find documentation describing this behavior but by testing it in Rust 1.37.0, it does appear to be the case.
Solution 3
The lib.rs
and main.rs
files are two independent entry points for your package.
When you use cargo run
(or build the binary and run it explicitly), the entry point to be used is main.rs
, and the crate
keyword refer to the binary crate. It doesn't even have to know that there is something in lib.rs
: the binary will treat the library as it would any other external crate, and it must be imported, through extern crate hello_world
or, for example, use hello_world::foo
.
When you import the library, however, the entry point is lib.rs
, and the crate
is the library crate. In this case, yes, all that you've added to lib.rs
is exposed to the whole crate.
The usual worksflow in this case is to make the binary something like a thin wrapper around the library - in some extreme cases the main.rs
would only contain something like
fn main() {
library::main();
}
and the whole logic (and all the project structure) goes into the library crate. One of the reasons is exactly what you've run into: the possible confusion whether this concrete module is imported in each crate in the package.
nz_21
Updated on January 21, 2022Comments
-
nz_21 over 2 years
I have 4 files:
main.rs
mod bar; fn main() { let v = vec![1, 2, 3]; println!("Hello, world!"); }
lib.rs
pub mod foo; pub mod bar;
foo.rs
pub fn say_foo() { }
bar.rs
use crate::foo; fn bar() { foo::say_foo(); }
When I run
cargo run
I get an error saying:error[E0432]: unresolved import `crate::foo` --> src/bar.rs:1:5 | 1 | use crate::foo; | ^^^^^^^^^^ no `foo` in the root
Could someone explain to me how to fix this? A bit more broadly: how does module lookup work when there's a
main.rs
and alib.rs
?Edit: Adding
mod foo
tomain.rs
fixes the issue. But I don't understand this -- I was under the impression thelib.rs
was the place that "exposed" all of my modules? Why do I have to declare the module inmain.rs
as well?My
Cargo.toml
:[package] name = "hello-world" version = "0.1.0" authors = ["[email protected]>"] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies]
-
L.Y. Sim over 4 years"
crate
refers tosrc/lib.rs
, because we're in the context of our library here." But hang on though, aren'tfoo
andbar
both in the same crate context, since they're both modules withinlib.rs
? Why wouldbar
have to refer tofoo
as if it were external? -
zrzka over 4 yearsYou mean
use crate::foo;
? Why do you think it's an external reference, it's not. Thecrate
keyword refers to the current crate, which is the library itself. You can useuse super::foo;
if you wish. -
L.Y. Sim over 4 yearsHi, I believe there might have been some confusion because OP wasn't clear in the question. The
bar
module was declared inlib.rs
and not inmain.rs
. Please see my answer to OP below for a more detailed response. -
zrzka over 4 yearsNo, the OP edited the question and tried to fix it by: Adding mod foo to main.rs fixes the issue.
-
L.Y. Sim over 4 yearsActually never mind, I might have made a mistake myself 😅
-
L.Y. Sim over 4 yearsRight it does appear that I was confused. Because OP was claiming that adding
mod bar
tomain.rs
solved the issue when in fact removing it is what will allow the code to compile since thenbar
will be properly declared as a module oflib.rs
anduse crate::foo
can then properly refer to thefoo
module inlib.rs
. -
simbro over 3 yearsThat's a great answer!
-
decades over 3 yearsYour post is confusing in the end. You are referring a src/lib.rs in your Cargo.toml while it was already renamed to utils.rs...
-
Vidy Videni about 3 years@zrzka, thanks for this very details explanation, I create an example following the
Same package layout
part, still got the same error, github.com/videni/rust-package-layout-demo -
BallpointBen over 2 yearsThis answer is very helpful. In other words, you can imagine that 1.
lib.rs
is really calledmod.rs
, 2. the foldersrc
is really calledname_of_my_crate
as given by thename
field ofCargo.toml
's[package]
table, and 3.main.rs
actually resides outside your crate (so you have to reference your crate by its name, not bycrate::
).