Cargo is Rustโs build system and package manager.
To create a new project with Cargo:
It will create a directory hello_cargo
with the following structure:
.
โโโ Cargo.toml
โโโ .gitignore
โโโ src
โโโ main.rs
Git repository will be initialized as well. If initialized in a directory with an existing git config, it wouldnโt override it by default.
In Rust, packages of code are referred to as crates.
Cargo expects your source files to live inside the src directory. The top-level project directory is just for README files, license information, configuration files, and anything else not related to your code.
Cargo understands Semantic Versioning (sometimes called SemVer), which is a standard for writing version numbers. The specifier
0.8.5
is actually shorthand for^0.8.5
, which means any version that is at least 0.8.5 but below 0.9.0.
Crates.io is where people in the Rust ecosystem post their open source Rust projects for others to use.
Crate
A crate is the smallest amount of code that the Rust compiler considers at a time. Even if you run rustc
rather than cargo
and pass a single source code file , the compiler considers that file to be a crate.
Crates can contain modules, and the modules may be defined in other files that get compiled with the crate.
A crate can come in one of two forms: a binary crate or a library crate.
- Binary crates are programs you can compile to an executable that you can run, such as a command-line program or a server. Each must have a function called
main
that defines what happens when the executable runs. - Library crates donโt have a
main
function, and they donโt compile to an executable. Instead, they define functionality intended to be shared with multiple projects.
Most of the time when Rustaceans say โcrateโ, they mean library crate, and they use โcrateโ interchangeably with the general programming concept of a โlibraryโ.
The crate root is a source file that the Rust compiler starts from and makes up the root module of your crate.
Package
A package is a bundle of one or more crates that provides a set of functionality. A package contains a Cargo.toml file that describes how to build those crates.
A package can contain as many binary crates as you like, but at most only one library crate. A package must contain at least one crate, whether thatโs a library or binary crate.
Cargo follows a convention that src/main.rs is the crate root of a binary crate with the same name as the package.
Likewise, Cargo knows that if the package directory contains src/lib.rs, the package contains a library crate with the same name as the package, and src/lib.rs is its crate root.
Cargo passes the crate root files to rustc
to build the library or binary.
If a package contains src/main.rs and src/lib.rs, it has two crates: a binary and a library, both with the same name as the package.
A package can have multiple binary crates by placing files in the src/bin directory: each file will be a separate binary crate.
Modules
Start from the crate root: When compiling a crate, the compiler first looks in the crate root file (usually src/lib.rs for a library crate or src/main.rs for a binary crate) for code to compile.
Declaring modules: In the crate root file, you can declare new modules; say, you declare a โgardenโ module with mod garden;
. The compiler will look for the moduleโs code in these places:
- Inline, within curly brackets that replace the semicolon following mod garden
- In the file src/garden.rs
- In the file src/garden/mod.rs
Declaring submodules: In any file other than the crate root, you can declare submodules. For example, you might declare mod vegetables;
in src/garden.rs. The compiler will look for the submoduleโs code within the directory named for the parent module in these places:
- Inline, directly following mod vegetables
, within curly brackets instead of the semicolon
- In the file src/garden/vegetables.rs
- In the file src/garden/vegetables/mod.rs
Paths to code in modules: Once a module is part of your crate, you can refer to code in that module from anywhere else in that same crate, as long as the privacy rules allow, using the path to the code. For example, an Asparagus
type in the garden vegetables module would be found at crate::garden::vegetables::Asparagus
.
Private vs public: Code within a module is private from its parent modules by default. To make a module public, declare it with pub mod
instead of mod
. To make items within a public module public as well, use pub
before their declarations.
The use
keyword: Within a scope, the use
keyword creates shortcuts to items to reduce repetition of long paths. In any scope that can refer to crate::garden::vegetables::Asparagus
, you can create a shortcut with use crate::garden::vegetables::Asparagus;
and from then on you only need to write Asparagus
to make use of that type in the scope.
Example:
backyard
โโโ Cargo.lock
โโโ Cargo.toml
โโโ src
โโโ garden
โ โโโ vegetables.rs
โโโ garden.rs
โโโ main.rs
src/main.rs and src/lib.rs are called crate roots. The reason for their name is that the contents of either of these two files form a module named crate
at the root of the crateโs module structure, known as the module tree.
For the code:
The module tree will look like this:
crate
โโโ front_of_house
โโโ hosting
โ โโโ add_to_waitlist
โ โโโ seat_at_table
โโโ serving
โโโ take_order
โโโ serve_order
โโโ take_payment
Notice that the entire module tree is rooted under the implicit module named crate
.
Each item in a module tree has a path. A path can take two forms:
- An absolute path is the full path starting from a crate root; for code from an external crate, the absolute path begins with the crate name, and for code from the current crate, it starts with the literal
crate
. - A relative path starts from the current module and uses
self
,super
, or an identifier in the current module. Both absolute and relative paths are followed by one or more identifiers separated by double colons::
.
Our preference in general is to specify absolute paths because itโs more likely weโll want to move code definitions and item calls independently of each other. https://doc.rust-lang.org/stable/book/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html
Privacy
In Rust, all items (functions, methods, structs, enums, modules, and constants) are private to parent modules by default.
Items in a parent module canโt use the private items inside child modules, but items in child modules can use the items in their ancestor modules. This is because child modules wrap and hide their implementation details, but the child modules can see the context in which theyโre defined.
Rust chose to have the module system function this way so that hiding inner implementation details is the default. That way, you know which parts of the inner code you can change without breaking outer code.
Rust does give you the option to expose inner parts of child modulesโ code to outer ancestor modules by using the pub
keyword to make an item public.
Making the module public doesnโt make its contents public. The pub
keyword on a module only lets code in its ancestor modules refer to it, not access its inner code.
Siblings can access each other. For example, in the example above eat_at_restaurant
function is defined in the same module as front_of_house
(that way they are siblings), we can refer to front_of_house
from eat_at_restaurant
Structs and enums
If we use pub
before a struct definition, we make the struct public, but the structโs fields will still be private. We can make each field public or not on a case-by-case basis.
If struct has a private field, we need to provide a public method to construct it, otherwise the client wouldnโt be able to instantiate a struct.
In contrast, if we make an enum public, all of its variants are then public. We only need the pub
before the enum
keyword.
Enums arenโt very useful unless their variants are public; it would be annoying to have to annotate all enum variants with pub in every case, so the default for enum variants is to be public. Structs are often useful without their fields being public, so struct fields follow the general rule of everything being private by default unless annotated with pub.
Best Practices for Packages with a Binary and a Library
We mentioned a package can contain both a src/main.rs binary crate root as well as a src/lib.rs library crate root, and both crates will have the package name by default. Typically, packages with this pattern of containing both a library and a binary crate will have just enough code in the binary crate to start an executable that calls code with the library crate. This lets other projects benefit from the most functionality that the package provides, because the library crateโs code can be shared.
The module tree should be defined in src/lib.rs. Then, any public items can be used in the binary crate by starting paths with the name of the package. The binary crate becomes a user of the library crate just like a completely external crate would use the library crate: it can only use the public API. This helps you design a good API; not only are you the author, youโre also a client!
super
in relative path
We can construct relative paths that begin in the parent module, rather than the current module or the crate root, by using super
at the start of the path.
Using super allows us to reference an item that we know is in the parent module, which can make rearranging the module tree easier when the module is closely related to the parent, but the parent might be moved elsewhere in the module tree someday.
use
keyword
We can create a shortcut to a path with the use
keyword once, and then use the shorter name everywhere else in the scope.
Paths brought into scope with use
also check privacy, like any other paths.
use
only creates the shortcut for the particular scope in which the use
occurs. Because of that this code wonโt compile:
Idiomatic way to bring a function into a scope is to bring it parent, so it would be obvious what is called and where is it:
On the other hand, when bringing in structs, enums, and other items with use
, itโs idiomatic to specify the full path.
Thereโs no strong reason behind this idiom: itโs just the convention that has emerged, and folks have gotten used to reading and writing Rust code this way.
The exception to this idiom is if weโre bringing two items with the same name into scope with use
statements.
You can also provide a new name (alias) with the as
keyword:
When we bring a name into scope with the use
keyword, the name available in the new scope is private. To enable the code that calls our code to refer to that name as if it had been defined in that codeโs scope, we can combine pub
and use
. This technique is called re-exporting because weโre bringing an item into scope but also making that item available for others to bring into their scope.
Re-exporting is useful when the internal structure of your code is different from how programmers calling your code would think about the domain. With pub use
, we can write our code with one structure but expose a different structure. Doing so makes our library well organized for programmers working on the library and programmers calling the library.
Nested paths
self
can be used to specify a current path:
Glob operator
If we want to bring all public items defined in a path into scope, we can specify that path followed by the *
glob operator:
Be careful when using the glob operator! Glob can make it harder to tell what names are in scope and where a name used in your program was defined.
The glob operator is often used when testing to bring everything under test into the tests
module.
The glob operator is also sometimes used as part of the prelude pattern: see the standard library documentation for more information on that pattern.
Separating modules into different files
To import a module:
Note that you only need to load a file using a mod
declaration once in your module tree. Once the compiler knows the file is part of the project (and knows where in the module tree the code resides because of where youโve put the mod
statement), other files in your project should refer to the loaded fileโs code using a path to where it was declared. In other words, mod
is not an โincludeโ operation that you may have seen in other programming languages.
So far weโve covered the most idiomatic file paths the Rust compiler uses, but Rust also supports an older style of file path. For a module named
front_of_house
declared in the crate root, the compiler will look for the moduleโs code in:
- src/front_of_house.rs (what we covered)
- src/front_of_house/mod.rs (older style, still supported path)
For a module named
hosting
that is a submodule offront_of_house
, the compiler will look for the moduleโs code in:
- src/front_of_house/hosting.rs (what we covered)
- src/front_of_house/hosting/mod.rs (older style, still supported path)
If you use both styles for the same module, youโll get a compiler error. Using a mix of both styles for different modules in the same project is allowed, but might be confusing for people navigating your project.
The main downside to the style that uses files named mod.rs is that your project can end up with many files named mod.rs, which can get confusing when you have them open in your editor at the same time.