Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust modules confusion when there is main.rs and lib.rs

Tags:

module

rust

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 a lib.rs?

Edit: Adding mod foo to main.rs fixes the issue. But I don't understand this -- I was under the impression the lib.rs was the place that "exposed" all of my modules? Why do I have to declare the module in main.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] 
like image 930
nz_21 Avatar asked Sep 02 '19 12:09

nz_21


People also ask

How do modules work in Rust?

Rust provides a powerful module system that can be used to hierarchically split code in logical units (modules), and manage visibility (public/private) between them. A module is a collection of items: functions, structs, traits, impl blocks, and even other modules.

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.

What is a library in Rust?

A collection of reusable, precompiled programs, scripts, or routines that can be called by the programmer while writing a code is called a Library in Rust.


2 Answers

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

  • Package Layout
  • The Manifest Format
  • Managing Growing Projects with Packages, Crates, and Modules
like image 151
zrzka Avatar answered Sep 30 '22 19:09

zrzka


In short the official Rust book has this to say:

If a package contains src/main.rs and src/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.

like image 23
L.Y. Sim Avatar answered Sep 30 '22 19:09

L.Y. Sim