From 8851ec647a40cabce60db23ae86318f3260a8206 Mon Sep 17 00:00:00 2001 From: Steve Klabnik Date: Fri, 26 Sep 2014 00:20:25 -0400 Subject: [PATCH] New Guide: crates and modules --- src/doc/guide-crates.md | 569 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 569 insertions(+) create mode 100644 src/doc/guide-crates.md diff --git a/src/doc/guide-crates.md b/src/doc/guide-crates.md new file mode 100644 index 0000000000000..8705bdd278ec7 --- /dev/null +++ b/src/doc/guide-crates.md @@ -0,0 +1,569 @@ +% The Rust Crates and Modules Guide + +When a project starts getting large, it's considered a good software +engineering practice to split it up into a bunch of smaller pieces, and then +fit them together. It's also important to have a well-defined interface, so +that some of your functionality is private, and some is public. To facilitate +these kinds of things, Rust has a module system. + +# Basic terminology: Crates and Modules + +Rust has two distinct terms that relate to the module system: "crate" and +"module." A crate is synonymous with a 'library' or 'package' in other +languages. Hence "Cargo" as the name of Rust's package management tool: you +ship your crates to others with Cargo. Crates can produce an executable or a +shared library, depending on the project. + +Each crate has an implicit "root module" that contains the code for that crate. +You can then define a tree of sub-modules under that root module. Modules allow +you to partition your code within the crate itself. + +As an example, let's make a "phrases" crate, which will give us various phrases +in different languages. To keep things simple, we'll stick to "greetings" and +"farewells" as two kinds of phrases, and use English and Japanese (日本語) as +two languages for those phrases to be in. We'll use this module layout: + +```text + +-----------+ + +---| greetings | + | +-----------+ + +---------+ | + | english |---+ + +---------+ | +-----------+ + | +---| farewells | ++---------+ | +-----------+ +| phrases |---+ ++---------+ | +-----------+ + | +---| greetings | + +----------+ | +-----------+ + | japanese |---+ + +----------+ | + | +-----------+ + +---| farewells | + +-----------+ +``` + +In this example, `phrases` is the name of our crate. All of the rest are +modules. You can see that they form a tree, branching out from the crate +"root", which is the root of the tree: `phrases` itself. + +Now that we have a plan, let's define these modules in code. To start, +generate a new crate with Cargo: + +```bash +$ cargo new phrases +$ cd phrases +``` + +If you remember, this generates a simple project for us: + +```bash +$ tree . +. +├── Cargo.toml +└── src + └── lib.rs + +1 directory, 2 files +``` + +`src/lib.rs` is our crate root, corresponding to the `phrases` in our diagram +above. + +# Defining Modules + +To define each of our modules, we use the `mod` keyword. Let's make our +`src/lib.rs` look like this: + +``` +// in src/lib.rs + +mod english { + mod greetings { + + } + + mod farewells { + + } +} + +mod japanese { + mod greetings { + + } + + mod farewells { + + } +} +``` + +After the `mod` keyword, you give the name of the module. Module names follow +the conventions for other Rust identifiers: `lower_snake_case`. The contents of +each module are within curly braces (`{}`). + +Within a given `mod`, you can declare sub-`mod`s. We can refer to sub-modules +with double-colon (`::`) notation: our four nested modules are +`english::greetings`, `english::farewells`, `japanese::greetings`, and +`japanese::farewells`. Because these sub-modules are namespaced under their +parent module, the names don't conflict: `english::greetings` and +`japanese::greetings` are distinct, even though their names are both +`greetings`. + +Because this crate does not have a `main()` function, and is called `lib.rs`, +Cargo will build this crate as a library: + +```bash +$ cargo build + Compiling phrases v0.0.1 (file:///home/you/projects/phrases) +$ ls target +deps libphrases-a7448e02a0468eaa.rlib native +``` + +`libphrase-hash.rlib` is the compiled crate. Before we see how to use this +crate from another crate, let's break it up into multiple files. + +# Multiple file crates + +If each crate were just one file, these files would get very large. It's often +easier to split up crates into multiple files, and Rust supports this in two +ways. + +Instead of declaring a module like this: + +```{rust,ignore} +mod english { + // contents of our module go here +} +``` + +We can instead declare our module like this: + +```{rust,ignore} +mod english; +``` + +If we do that, Rust will expect to find either a `english.rs` file, or a +`english/mod.rs` file with the contents of our module: + +```{rust,ignore} +// contents of our module go here +``` + +Note that in these files, you don't need to re-declare the module: that's +already been done with the initial `mod` declaration. + +Using these two techniques, we can break up our crate into two directories and +seven files: + +```bash +$ tree . +. +├── Cargo.lock +├── Cargo.toml +├── src +│   ├── english +│   │   ├── farewells.rs +│   │   ├── greetings.rs +│   │   └── mod.rs +│   ├── japanese +│   │   ├── farewells.rs +│   │   ├── greetings.rs +│   │   └── mod.rs +│   └── lib.rs +└── target + ├── deps + ├── libphrases-a7448e02a0468eaa.rlib + └── native +``` + +`src/lib.rs` is our crate root, and looks like this: + +```{rust,ignore} +// in src/lib.rs + +mod english; + +mod japanese; +``` + +These two declarations tell Rust to look for either `src/english.rs` and +`src/japanese.rs`, or `src/english/mod.rs` and `src/japanese/mod.rs`, depending +on our preference. In this case, because our modules have sub-modules, we've +chosen the second. Both `src/english/mod.rs` and `src/japanese/mod.rs` look +like this: + +```{rust,ignore} +// both src/english/mod.rs and src/japanese/mod.rs + +mod greetings; + +mod farewells; +``` + +Again, these declarations tell Rust to look for either +`src/english/greetings.rs` and `src/japanese/greetings.rs` or +`src/english/farewells/mod.rs` and `src/japanese/farewells/mod.rs`. Because +these sub-modules don't have their own sub-modules, we've chosen to make them +`src/english/greetings.rs` and `src/japanese/farewells.rs`. Whew! + +Right now, the contents of `src/english/greetings.rs` and +`src/japanese/farewells.rs` are both empty at the moment. Let's add some +functions. + +Put this in `src/english/greetings.rs`: + +```rust +// in src/english/greetings.rs + +fn hello() -> String { + "Hello!".to_string() +} +``` + +Put this in `src/english/farewells.rs`: + +```rust +// in src/english/farewells.rs + +fn goodbye() -> String { + "Goodbye.".to_string() +} +``` + +Put this in `src/japanese/greetings.rs`: + +```rust +// in src/japanese/greetings.rs + +fn hello() -> String { + "こんにちは".to_string() +} +``` + +Of course, you can copy and paste this from this web page, or just type +something else. It's not important that you actually put "konnichiwa" to learn +about the module system. + +Put this in `src/japanese/farewells.rs`: + +```rust +// in src/japanese/farewells.rs + +fn goodbye() -> String { + "さようなら".to_string() +} +``` + +(This is "Sayoonara", if you're curious.) + +Now that we have our some functionality in our crate, let's try to use it from +another crate. + +# Importing External Crates + +We have a library crate. Let's make an executable crate that imports and uses +our library. + +Make a `src/main.rs` and put this in it: (it won't quite compile yet) + +```rust,ignore +// in src/main.rs + +extern crate phrases; + +fn main() { + println!("Hello in English: {}", phrases::english::greetings::hello()); + println!("Goodbye in English: {}", phrases::english::farewells::goodbye()); + + println!("Hello in Japanese: {}", phrases::japanese::greetings::hello()); + println!("Goodbye in Japanese: {}", phrases::japanese::farewells::goodbye()); +} +``` + +The `extern crate` declaration tells Rust that we need to compile and link to +the `phrases` crate. We can then use `phrases`' modules in this one. As we +mentioned earlier, you can use double colons to refer to sub-modules and the +functions inside of them. + +Also, Cargo assumes that `src/main.rs` is the crate root of a binary crate, +rather than a library crate. Once we compile `src/main.rs`, we'll get an +executable that we can run. Our package now has two crates: `src/lib.rs` and +`src/main.rs`. This pattern is quite common for executable crates: most +functionality is in a library crate, and the executable crate uses that +library. This way, other programs can also use the library crate, and it's also +a nice separation of concerns. + +This doesn't quite work yet, though. We get four errors that look similar to +this: + +```bash +$ cargo build + Compiling phrases v0.0.1 (file:///home/you/projects/phrases) +/home/you/projects/phrases/src/main.rs:4:38: 4:72 error: function `hello` is private +/home/you/projects/phrases/src/main.rs:4 println!("Hello in English: {}", phrases::english::greetings::hello()); + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +note: in expansion of format_args! +:2:23: 2:77 note: expansion site +:1:1: 3:2 note: in expansion of println! +/home/you/projects/phrases/src/main.rs:4:5: 4:76 note: expansion site + +``` + +By default, everything is private in Rust. Let's talk about this in some more +depth. + +# Exporting a Public Interface + +Rust allows you to precisely control which aspects of your interface are +public, and so private is the default. To make things public, you use the `pub` +keyword. Let's focus on the `english` module first, so let's reduce our `src/main.rs` +to just this: + +```{rust,ignore} +// in src/main.rs + +extern crate phrases; + +fn main() { + println!("Hello in English: {}", phrases::english::greetings::hello()); + println!("Goodbye in English: {}", phrases::english::farewells::goodbye()); +} +``` + +In our `src/lib.rs`, let's add `pub` to the `english` module declaration: + +```{rust,ignore} +// in src/lib.rs + +pub mod english; + +mod japanese; +``` + +And in our `src/english/mod.rs`, let's make both `pub`: + +```{rust,ignore} +// in src/english/mod.rs + +pub mod greetings; + +pub mod farewells; +``` + +In our `src/english/greetings.rs`, let's add `pub` to our `fn` declaration: + +```{rust,ignore} +// in src/english/greetings.rs + +pub fn hello() -> String { + "Hello!".to_string() +} +``` + +And also in `src/english/farewells.rs`: + +```{rust,ignore} +// in src/english/farewells.rs + +pub fn goodbye() -> String { + "Goodbye.".to_string() +} +``` + +Now, our crate compiles, albeit with warnings about not using the `japanese` +functions: + +```bash +$ cargo run + Compiling phrases v0.0.1 (file:///home/you/projects/phrases) +/home/you/projects/phrases/src/japanese/greetings.rs:1:1: 3:2 warning: code is never used: `hello`, #[warn(dead_code)] on by default +/home/you/projects/phrases/src/japanese/greetings.rs:1 fn hello() -> String { +/home/you/projects/phrases/src/japanese/greetings.rs:2 "こんにちは".to_string() +/home/you/projects/phrases/src/japanese/greetings.rs:3 } +/home/you/projects/phrases/src/japanese/farewells.rs:1:1: 3:2 warning: code is never used: `goodbye`, #[warn(dead_code)] on by default +/home/you/projects/phrases/src/japanese/farewells.rs:1 fn goodbye() -> String { +/home/you/projects/phrases/src/japanese/farewells.rs:2 "さようなら".to_string() +/home/you/projects/phrases/src/japanese/farewells.rs:3 } + Running `target/phrases` +Hello in English: Hello! +Goodbye in English: Goodbye. +``` + +Now that our functions are public, we can use them. Great! However, typing out +`phrases::english::greetings::hello()` is very long and repetitive. Rust has +another keyword for importing names into the current scope, so that you can +refer to them with shorter names. Let's talk about `use`. + +# Importing Modules with `use` + +Rust has a `use` keyword, which allows us to import names into our local scope. +Let's change our `src/main.rs` to look like this: + +```{rust,ignore} +// in src/main.rs + +extern crate phrases; + +use phrases::english::greetings; +use phrases::english::farewells; + +fn main() { + println!("Hello in English: {}", greetings::hello()); + println!("Goodbye in English: {}", farewells::goodbye()); +} +``` + +The two `use` lines import each module into the local scope, so we can refer to +the functions by a much shorter name. By convention, when importing functions, it's +considered best practice to import the module, rather than the function directly. In +other words, you _can_ do this: + +```{rust,ignore} +extern crate phrases; + +use phrases::english::greetings::hello; +use phrases::english::farewells::goodbye; + +fn main() { + println!("Hello in English: {}", hello()); + println!("Goodbye in English: {}", goodbye()); +} +``` + +But it is not idiomatic. This is significantly more likely to introducing a +naming conflict. In our short program, it's not a big deal, but as it grows, it +becomes a problem. If we have conflicting names, Rust will give a compilation +error. For example, if we made the `japanese` functions public, and tried to do +this: + +```{rust,ignore} +extern crate phrases; + +use phrases::english::greetings::hello; +use phrases::japanese::greetings::hello; + +fn main() { + println!("Hello in English: {}", hello()); + println!("Hello in Japanese: {}", hello()); +} +``` + +Rust will give us a compile-time error: + +```{rust,ignore} + Compiling phrases v0.0.1 (file:///home/you/projects/phrases) +/home/you/projects/phrases/src/main.rs:4:5: 4:40 error: a value named `hello` has already been imported in this module +/home/you/projects/phrases/src/main.rs:4 use phrases::japanese::greetings::hello; + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +error: aborting due to previous error +Could not compile `phrases`. +``` + +If we're importing multiple names from the same module, we don't have to type it out +twice. Rust has a shortcut syntax for writing this: + +```{rust,ignore} +use phrases::english::greetings; +use phrases::english::farewells; +``` + +You use curly braces: + +```{rust,ignore} +use phrases::english::{greetings, farewells}; +``` + +These two declarations are equivalent, but the second is a lot less typing. + +## Re-exporting with `pub use` + +You don't just use `use` to shorten identifiers. You can also use it inside of your crate +to re-export a function inside another module. This allows you to present an external +interface that may not directly map to your internal code organization. + +Let's look at an example. Modify your `src/main.rs` to read like this: + +```{rust,ignore} +// in src/main.rs + +extern crate phrases; + +use phrases::english::{greetings,farewells}; +use phrases::japanese; + +fn main() { + println!("Hello in English: {}", greetings::hello()); + println!("Goodbye in English: {}", farewells::goodbye()); + + println!("Hello in Japanese: {}", japanese::hello()); + println!("Goodbye in Japanese: {}", japanese::goodbye()); +} +``` + +Then, modify your `src/lib.rs` to make the `japanese` mod public: + +```{rust,ignore} +// in src/lib.rs + +pub mod english; + +pub mod japanese; +``` + +Next, make the two functions public, first in `src/japanese/greetings.rs`: + +```{rust,ignore} +// in src/japanese/greetings.rs + +pub fn hello() -> String { + "こんにちは".to_string() +} +``` + +And then in `src/japanese/farewells.rs`: + +```{rust,ignore} +// in src/japanese/farewells.rs + +pub fn goodbye() -> String { + "さようなら".to_string() +} +``` + +Finally, modify your `src/japanese/mod.rs` to read like this: + +```{rust,ignore} +// in src/japanese/mod.rs + +pub use self::greetings::hello; +pub use self::farewells::goodbye; + +mod greetings; + +mod farewells; +``` + +The `pub use` declaration brings the function into scope at this part of our +module hierarchy. Because we've `pub use`d this inside of our `japanese` +module, we now have a `phrases::japanese::hello()` function and a +`phrases::japanese::goodbye()` function, even though the code for them lives in +`phrases::japanese::greetings::hello()` and +`phrases::japanese::farewells::goodbye()`. Our internal organization doesn't +define our external interface. + +Also, note that we `pub use`d before we declared our `mod`s. Rust requires that +`use` declarations go first. + +This will build and run: + +```bash +$ cargo build + Compiling phrases v0.0.1 (file:///home/you/projects/phrases) + Running `target/phrases` +Hello in English: Hello! +Goodbye in English: Goodbye. +Hello in Japanese: こんにちは +Goodbye in Japanese: さようなら +```