Modularizing
Modularizing in different files
-
Let's say we decided to put some code in the file
src/front_of_house.rs
, and we want to use this code inside the filesrc/lib.rs
. It can be done like this:src ├── front_of_house.rs └── lib.rs
#![allow(unused)] fn main() { // Filename: src/lib.rs // This will bring the contents of module (thst is stored in file `src/front_of_house.rs`) // into the current file mod front_of_house; // This will allow us to use as well as export it // so that external can use it too. pub use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); hosting::add_to_waitlist(); hosting::add_to_waitlist(); } }
-
Now, we can make a new directory as well and can store the files as folows:
src ├── front_of_house │ └── hosting.rs ├── front_of_house.rs └── lib.rs
#![allow(unused)] fn main() { // Filename: src/front_of_house.rs pub mod hosting; }
#![allow(unused)] fn main() { // Filename: src/front_of_house/hosting.rs pub fn add_to_waitlist() {} }
Re-exporting
-
You may use re-exporting, for making it easier to use your crate for other developers. It allows to use the structures directly intead of following the heirarchy in which the crate is designed.
-
Without re-exporting:
-
How structure looks:
#![allow(unused)] fn main() { pub mod kinds { pub enum PrimaryColor { ... } pub enum SecondaryColor { ... } } pub mod utils { use crate::kinds::*; pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor { ... } } }
-
How others will be using it:
use art::kinds::PrimaryColor; use art::utils::mix; fn main() { let red = PrimaryColor::Red; let yellow = PrimaryColor::Yellow; mix(red, yellow); }
-
-
With re-exporting:
-
How structure looks:
#![allow(unused)] fn main() { // Here we're re-exporting it for direct use pub use self::kinds::PrimaryColor; pub use self::kinds::SecondaryColor; pub use self::utils::mix; pub mod kinds { ... } pub mod utils { ... } }
-
How others will be using it:
// Isn't it easier to import now? use art::mix; use art::PrimaryColor; fn main() { let red = PrimaryColor::Red; let yellow = PrimaryColor::Yellow; mix(red, yellow); }
-
-
Workspaces
-
A
workspace
is a set of packages that share the sameCargo.lock
and output directory. -
You can build your workspace that looks like this:
add ├── Cargo.lock ├── Cargo.toml ├── add_one │ ├── Cargo.toml │ └── src │ └── lib.rs ├── adder │ ├── Cargo.toml │ └── src │ └── main.rs └── target // Notice only one target directory
-
The
cargo.toml
ofadd
(the outer one) of the workspace will look like this:<!-- Filename: add/Cargo.toml --> [workspace] members = [ "adder", "add_one", ]
-
The workspace has one target directory at the top level, the
adder
package doesn’t have its owntarget
directory. -
Even if we were to run
cargo build
from inside theadder
directory, the compiled artifacts would still end up inadd/target
rather thanadd/adder/target
. -
The
cargo.toml
ofadder
will look like this:<!-- Filename: add/adder/Cargo.toml --> [dependencies] add_one = { path = "../add_one" }
-
The
main.rs
inadder
will look something like this:// Filename: add/adder/src/main.rs use add_one; fn main() { let num = 10; println!( "Hello, world! {} plus one is {}!", num, add_one::add_one(num) ); }
-
To build the whole workspace, you may run this command from the
add
directory (the outer).$ cargo build Compiling add_one v0.1.0 (file:///projects/add/add_one) Compiling adder v0.1.0 (file:///projects/add/adder) Finished dev [unoptimized + debuginfo] target(s) in 0.68s
-
To run a particular package you may run the following command:
$ cargo run -p adder Finished dev [unoptimized + debuginfo] target(s) in 0.0s Running `target/debug/adder` Hello, world! 10 plus one is 11!
-
All the dependencies in different packages will use the same version of the dependency. It is because the
cargo.toml
of the workspace will make only one entry of the dependency. It also saves space and makes all the package compatible with each other, since they'll be using the same version of the dependency. -
To run all test:
cargo run test
-
To run test in particular file:
cargo test -p add_one
Refactoring Guides
This pattern is about separating concerns: main.rs
handles running the program, and lib.rs
handles all the logic of the task at hand. Because you can’t test the main function directly, this structure lets you test all of your program’s logic by moving it into functions in lib.rs
. The only code that remains in main.rs will be small enough to verify its correctness by reading it.