返回首页返回书籍《The Rust Programming Language》
目录

目录 · CONTENTS

  1. 01The Rust Programming Language
  2. 02Foreword
  3. 03Introduction
  4. 04Getting Started
  5. 05Installation
  6. 06Hello, World!
  7. 07Hello, Cargo!
  8. 08Programming a Guessing Game
  9. 09Common Programming Concepts
  10. 10Variables and Mutability
  11. 11Data Types
  12. 12Functions
  13. 13Comments
  14. 14Control Flow
  15. 15Understanding Ownership
  16. 16What is Ownership?
  17. 17References and Borrowing
  18. 18The Slice Type
  19. 19Using Structs to Structure Related Data
  20. 20Defining and Instantiating Structs
  21. 21An Example Program Using Structs
  22. 22Methods
  23. 23Enums and Pattern Matching
  24. 24Defining an Enum
  25. 25The `match` Control Flow Construct
  26. 26Concise Control Flow with `if let` and `let...else`
  27. 27Packages, Crates, and Modules
  28. 28Packages and Crates
  29. 29Control Scope and Privacy with Modules
  30. 30Paths for Referring to an Item in the Module Tree
  31. 31Bringing Paths Into Scope with the `use` Keyword
  32. 32Separating Modules into Different Files
  33. 33Common Collections
  34. 34Storing Lists of Values with Vectors
  35. 35Storing UTF-8 Encoded Text with Strings
  36. 36Storing Keys with Associated Values in Hash Maps
  37. 37Error Handling
  38. 38Unrecoverable Errors with `panic!`
  39. 39Recoverable Errors with `Result`
  40. 40To `panic!` or Not to `panic!`
  41. 41Generic Types, Traits, and Lifetimes
  42. 42Generic Data Types
  43. 43Defining Shared Behavior with Traits
  44. 44Validating References with Lifetimes
  45. 45Writing Automated Tests
  46. 46How to Write Tests
  47. 47Controlling How Tests Are Run
  48. 48Test Organization
  49. 49An I/O Project: Building a Command Line Program
  50. 50Accepting Command Line Arguments
  51. 51Reading a File
  52. 52Refactoring to Improve Modularity and Error Handling
  53. 53Adding Functionality with Test Driven Development
  54. 54Working with Environment Variables
  55. 55Redirecting Errors to Standard Error
  56. 56Functional Language Features: Iterators and Closures
  57. 57Closures
  58. 58Processing a Series of Items with Iterators
  59. 59Improving Our I/O Project
  60. 60Performance in Loops vs. Iterators
  61. 61More About Cargo and Crates.io
  62. 62Customizing Builds with Release Profiles
  63. 63Publishing a Crate to Crates.io
  64. 64Cargo Workspaces
  65. 65Installing Binaries with `cargo install`
  66. 66Extending Cargo with Custom Commands
  67. 67Smart Pointers
  68. 68Using `Box<T>` to Point to Data on the Heap
  69. 69Treating Smart Pointers Like Regular References
  70. 70Running Code on Cleanup with the `Drop` Trait
  71. 71`Rc<T>`, the Reference Counted Smart Pointer
  72. 72`RefCell<T>` and the Interior Mutability Pattern
  73. 73Reference Cycles Can Leak Memory
  74. 74Fearless Concurrency
  75. 75Using Threads to Run Code Simultaneously
  76. 76Transfer Data Between Threads with Message Passing
  77. 77Shared-State Concurrency
  78. 78Extensible Concurrency with `Send` and `Sync`
  79. 79Fundamentals of Asynchronous Programming: Async, Await, Futures, and Streams
  80. 80extern crate trpl; // required for mdbook test
  81. 81Applying Concurrency with Async
  82. 82Working With Any Number of Futures
  83. 83Streams: Futures in Sequence
  84. 84A Closer Look at the Traits for Async
  85. 85Futures, Tasks, and Threads
  86. 86Object-Oriented Programming Features
  87. 87Characteristics of Object-Oriented Languages
  88. 88Using Trait Objects to Abstract over Shared Behavior
  89. 89Implementing an Object-Oriented Design Pattern
  90. 90Patterns and Matching
  91. 91All the Places Patterns Can Be Used
  92. 92Refutability: Whether a Pattern Might Fail to Match
  93. 93Pattern Syntax
  94. 94Advanced Features
  95. 95Unsafe Rust
  96. 96Advanced Traits
  97. 97Advanced Types
  98. 98Advanced Functions and Closures
  99. 99Macros
  100. 100Final Project: Building a Multithreaded Web Server
  101. 101Building a Single-Threaded Web Server
  102. 102From Single-Threaded to Multithreaded Server
  103. 103Graceful Shutdown and Cleanup
  104. 104Appendix
  105. 105A - Keywords
  106. 106B - Operators and Symbols
  107. 107C - Derivable Traits
  108. 108D - Useful Development Tools
  109. 109E - Editions
  110. 110F - Translations of the Book
  111. 111G - How Rust is Made and “Nightly Rust”
第 31 / 111 节

第 31 节 · SECTION 31

Bringing Paths Into Scope with the `use` Keyword

Bringing Paths into Scope with the use Keyword

Having to write out the paths to call functions can feel inconvenient and repetitive. In Listing 7-7, whether we chose the absolute or relative path to the add_to_waitlist function, every time we wanted to call add_to_waitlist we had to specify front_of_house and hosting too. Fortunately, there’s a way to simplify this process: We can create a shortcut to a path with the use keyword once and then use the shorter name everywhere else in the scope.

In Listing 7-11, we bring the crate::front_of_house::hosting module into the scope of the eat_at_restaurant function so that we only have to specify hosting::add_to_waitlist to call the add_to_waitlist function in eat_at_restaurant.

Listing 7-11: Bringing a module into scope with use Filename: src/lib.rs

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}

Adding use and a path in a scope is similar to creating a symbolic link in the filesystem. By adding use crate::front_of_house::hosting in the crate root, hosting is now a valid name in that scope, just as though the hosting module had been defined in the crate root. Paths brought into scope with use also check privacy, like any other paths.

Note that use only creates the shortcut for the particular scope in which the use occurs. Listing 7-12 moves the eat_at_restaurant function into a new child module named customer, which is then a different scope than the use statement, so the function body won’t compile.

Listing 7-12: A use statement only applies in the scope it’s in. Filename: src/lib.rs

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

mod customer {
    pub fn eat_at_restaurant() {
        hosting::add_to_waitlist();
    }
}

The compiler error shows that the shortcut no longer applies within the customer module:

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0433]: cannot find module or crate `hosting` in this scope
  --> src/lib.rs:11:9
   |
11 |         hosting::add_to_waitlist();
   |         ^^^^^^^ use of unresolved module or unlinked crate `hosting`
   |
   = help: if you wanted to use a crate named `hosting`, use `cargo add hosting` to add it to your `Cargo.toml`
help: consider importing this module through its public re-export
   |
10 +     use crate::hosting;
   |

warning: unused import: `crate::front_of_house::hosting`
 --> src/lib.rs:7:5
  |
7 | use crate::front_of_house::hosting;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default

For more information about this error, try `rustc --explain E0433`.
warning: `restaurant` (lib) generated 1 warning
error: could not compile `restaurant` (lib) due to 1 previous error; 1 warning emitted

Notice there’s also a warning that the use is no longer used in its scope! To fix this problem, move the use within the customer module too, or reference the shortcut in the parent module with super::hosting within the child customer module.

Creating Idiomatic use Paths

In Listing 7-11, you might have wondered why we specified use crate::front_of_house::hosting and then called hosting::add_to_waitlist in eat_at_restaurant, rather than specifying the use path all the way out to the add_to_waitlist function to achieve the same result, as in Listing 7-13.

Listing 7-13: Bringing the add_to_waitlist function into scope with use, which is unidiomatic Filename: src/lib.rs

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting::add_to_waitlist;

pub fn eat_at_restaurant() {
    add_to_waitlist();
}

Although both Listing 7-11 and Listing 7-13 accomplish the same task, Listing 7-11 is the idiomatic way to bring a function into scope with use. Bringing the function’s parent module into scope with use means we have to specify the parent module when calling the function. Specifying the parent module when calling the function makes it clear that the function isn’t locally defined while still minimizing repetition of the full path. The code in Listing 7-13 is unclear as to where add_to_waitlist is defined.

On the other hand, when bringing in structs, enums, and other items with use, it’s idiomatic to specify the full path. Listing 7-14 shows the idiomatic way to bring the standard library’s HashMap struct into the scope of a binary crate.

Listing 7-14: Bringing HashMap into scope in an idiomatic way Filename: src/main.rs

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
}

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, because Rust doesn’t allow that. Listing 7-15 shows how to bring two Result types into scope that have the same name but different parent modules, and how to refer to them.

Listing 7-15: Bringing two types with the same name into the same scope requires using their parent modules. Filename: src/lib.rs

use std::fmt;
use std::io;

fn function1() -> fmt::Result {
    // --snip--
}

fn function2() -> io::Result<()> {
    // --snip--
}

As you can see, using the parent modules distinguishes the two Result types. If instead we specified use std::fmt::Result and use std::io::Result, we’d have two Result types in the same scope, and Rust wouldn’t know which one we meant when we used Result.

Providing New Names with the as Keyword

There’s another solution to the problem of bringing two types of the same name into the same scope with use: After the path, we can specify as and a new local name, or alias, for the type. Listing 7-16 shows another way to write the code in Listing 7-15 by renaming one of the two Result types using as.

Listing 7-16: Renaming a type when it’s brought into scope with the as keyword Filename: src/lib.rs

use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
    // --snip--
}

fn function2() -> IoResult<()> {
    // --snip--
}

In the second use statement, we chose the new name IoResult for the std::io::Result type, which won’t conflict with the Result from std::fmt that we’ve also brought into scope. Listing 7-15 and Listing 7-16 are considered idiomatic, so the choice is up to you!

Re-exporting Names with pub use

When we bring a name into scope with the use keyword, the name is private to the scope into which we imported it. To enable code outside that scope to refer to that name as if it had been defined in that 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.

Listing 7-17 shows the code in Listing 7-11 with use in the root module changed to pub use.

Listing 7-17: Making a name available for any code to use from a new scope with pub use Filename: src/lib.rs

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}

Before this change, external code would have to call the add_to_waitlist function by using the path restaurant::front_of_house::hosting::add_to_waitlist(), which also would have required the front_of_house module to be marked as pub. Now that this pub use has re-exported the hosting module from the root module, external code can use the path restaurant::hosting::add_to_waitlist() instead.

Re-exporting is useful when the internal structure of your code is different from how programmers calling your code would think about the domain. For example, in this restaurant metaphor, the people running the restaurant think about “front of house” and “back of house.” But customers visiting a restaurant probably won’t think about the parts of the restaurant in those terms. 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. We’ll look at another example of pub use and how it affects your crate’s documentation in “Exporting a Convenient Public API” in Chapter 14.

Using External Packages

In Chapter 2, we programmed a guessing game project that used an external package called rand to get random numbers. To use rand in our project, we added this line to Cargo.toml:

Filename: Cargo.toml

rand = "0.10.1"

Adding rand as a dependency in Cargo.toml tells Cargo to download the rand package and any dependencies from crates.io and make rand available to our project.

Then, to bring rand definitions into the scope of our package, we added a use line starting with the name of the crate, rand, and listed the items we wanted to bring into scope. Recall that in “Generating a Random Number” in Chapter 2, we brought items in the rand::prelude module into scope and called the rand::rng function:

use rand::prelude::*;

fn main() {
    let secret_number = rand::rng().random_range(1..=100);
}

Members of the Rust community have made many packages available at crates.io, and pulling any of them into your package involves these same steps: listing them in your package’s Cargo.toml file and using use to bring items from their crates into scope.

Note that the standard std library is also a crate that’s external to our package. Because the standard library is shipped with the Rust language, we don’t need to change Cargo.toml to include std. But we do need to refer to it with use to bring items from there into our package’s scope. For example, with HashMap we would use this line:

use std::collections::HashMap;

This is an absolute path starting with std, the name of the standard library crate.

Using Nested Paths to Clean Up use Lists

If we’re using multiple items defined in the same crate or same module, listing each item on its own line can take up a lot of vertical space in our files. For example, these two use statements we had in the guessing game in Listing 2-4 bring items from std into scope:

Filename: src/main.rs

// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--

Instead, we can use nested paths to bring the same items into scope in one line. We do this by specifying the common part of the path, followed by two colons, and then curly brackets around a list of the parts of the paths that differ, as shown in Listing 7-18.

Listing 7-18: Specifying a nested path to bring multiple items with the same prefix into scope Filename: src/main.rs

// --snip--
use std::{cmp::Ordering, io};
// --snip--

In bigger programs, bringing many items into scope from the same crate or module using nested paths can reduce the number of separate use statements needed by a lot!

We can use a nested path at any level in a path, which is useful when combining two use statements that share a subpath. For example, Listing 7-19 shows two use statements: one that brings std::io into scope and one that brings std::io::Write into scope.

Listing 7-19: Two use statements where one is a subpath of the other Filename: src/lib.rs

use std::io;
use std::io::Write;

The common part of these two paths is std::io, and that’s the complete first path. To merge these two paths into one use statement, we can use self in the nested path, as shown in Listing 7-20.

Listing 7-20: Combining the paths in Listing 7-19 into one use statement Filename: src/lib.rs

use std::io::{self, Write};

This line brings std::io and std::io::Write into scope.

Importing Items with the 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:

use std::collections::*;

This use statement brings all public items defined in std::collections into the current scope. 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. Additionally, if the dependency changes its definitions, what you’ve imported changes as well, which may lead to compiler errors when you upgrade the dependency if the dependency adds a definition with the same name as a definition of yours in the same scope, for example.

The glob operator is often used when testing to bring everything under test into the tests module; we’ll talk about that in “How to Write Tests” in Chapter 11. The glob operator is also sometimes used as part of the prelude pattern: See the standard library documentation for more information on that pattern.