mirror of
https://github.com/rust-lang-cn/book-cn.git
synced 2025-01-23 23:50:25 +08:00
Standardize on fancy quotes
This commit is contained in:
parent
e8e42f79cb
commit
fad86c54a2
@ -1,6 +1,6 @@
|
||||
# Common Programming Concepts in Rust
|
||||
|
||||
Let's first look at concepts that appear in almost every programming language
|
||||
Let’s first look at concepts that appear in almost every programming language
|
||||
and see how they work in Rust. Many programming languages have much in common
|
||||
at their core. None of the concepts presented in this chapter are unique to
|
||||
Rust, but we’ll discuss Rust’s particular syntax and conventions concerning
|
||||
|
@ -3,12 +3,12 @@
|
||||
We mentioned in Chapter 2 that by default, variables are *immutable*.
|
||||
This is one of many nudges in Rust that encourages us to write our code in a
|
||||
way that gets the most of the safety and easy concurrency that Rust has to
|
||||
offer. We still have the option to make our variables mutable, though. Let's
|
||||
offer. We still have the option to make our variables mutable, though. Let’s
|
||||
explore how and why Rust encourages us to favor immutability, and why we might
|
||||
want to opt out of that.
|
||||
|
||||
Variables being immutable means once a value is bound, you can't
|
||||
change that value. To illustrate this, let's generate a new project in your
|
||||
Variables being immutable means once a value is bound, you can’t
|
||||
change that value. To illustrate this, let’s generate a new project in your
|
||||
projects directory called `variables` by using `cargo new --bin variables`.
|
||||
|
||||
Then, in your new `variables` directory, open `src/main.rs` and replace its code
|
||||
@ -43,17 +43,17 @@ note: prior assignment occurs here
|
||||
|
||||
This is our first example of the compiler helping us find an error in our
|
||||
program! Compiler errors can be frustrating. Keep in mind that they only mean
|
||||
your program isn't safely doing what you want it to do yet; they do *not* mean
|
||||
that you're not a good programmer! Experienced Rustaceans still get compiler
|
||||
your program isn’t safely doing what you want it to do yet; they do *not* mean
|
||||
that you’re not a good programmer! Experienced Rustaceans still get compiler
|
||||
errors. The Rust compiler is trying to help your program be the very best.
|
||||
|
||||
<!-- PROD: START BOX -->
|
||||
|
||||
> #### Extended Error Explanations
|
||||
>
|
||||
> Now that you've seen a Rust error, let's take a moment to look at one
|
||||
> Now that you’ve seen a Rust error, let’s take a moment to look at one
|
||||
> particularly useful aspect of errors. Rust encourages you to seek further
|
||||
> information on the kind of error you've received with output like this:
|
||||
> information on the kind of error you’ve received with output like this:
|
||||
>
|
||||
> ```bash
|
||||
> error: re-assignment of immutable variable `x` [--explain E0384]
|
||||
@ -78,8 +78,8 @@ errors. The Rust compiler is trying to help your program be the very best.
|
||||
> ```
|
||||
> ````
|
||||
>
|
||||
> These explanations can really help if you’re stuck on an error, so don't
|
||||
> hesitate to look up the error code. The compiler is your friend, and it's
|
||||
> These explanations can really help if you’re stuck on an error, so don’t
|
||||
> hesitate to look up the error code. The compiler is your friend, and it’s
|
||||
> there to help.
|
||||
|
||||
<!-- PROD: END BOX -->
|
||||
@ -88,17 +88,17 @@ The error tells us that the cause of the error is `re-assignment of immutable
|
||||
variable`, because we tried to assign a second value to the immutable `x`
|
||||
variable.
|
||||
|
||||
It's important that we get compile-time errors when we attempt to change a
|
||||
It’s important that we get compile-time errors when we attempt to change a
|
||||
value that we previously said was immutable because this very situation can
|
||||
lead to bugs. If one part of our code operates on an assumption that a value
|
||||
will never change, and another part of our code changes that value, it's
|
||||
possible that the first part of the code won't do what it was designed to do.
|
||||
will never change, and another part of our code changes that value, it’s
|
||||
possible that the first part of the code won’t do what it was designed to do.
|
||||
This cause of bugs can be difficult to track down after the fact, especially
|
||||
when the second piece of code only changes the value *sometimes*.
|
||||
|
||||
In Rust, we can trust that a value we say won't change really won't change,
|
||||
In Rust, we can trust that a value we say won’t change really won’t change,
|
||||
because the compiler is enforcing that guarantee for us. When reading and
|
||||
writing code, we don't have to keep track in our head how and where a value
|
||||
writing code, we don’t have to keep track in our head how and where a value
|
||||
might change. This can make code easier to reason about.
|
||||
|
||||
Mutability can be really useful, though! Variables are immutable only by
|
||||
@ -131,9 +131,9 @@ The value of x is: 6
|
||||
```
|
||||
|
||||
Using `mut`, we are allowed to change the value that `x` binds to from `5` to
|
||||
`6`. In some cases you'll want to make a variable mutable because it makes the
|
||||
`6`. In some cases you’ll want to make a variable mutable because it makes the
|
||||
code easier to understand than an implementation that only uses immutable
|
||||
variables. In cases where you're using large data structures, mutating an
|
||||
variables. In cases where you’re using large data structures, mutating an
|
||||
instance in place may be faster than copying and returning newly allocated
|
||||
instances. It all depends on the tradeoffs you want to make in your situation.
|
||||
|
||||
@ -142,8 +142,8 @@ instances. It all depends on the tradeoffs you want to make in your situation.
|
||||
As we saw in the guessing game tutorial, we can declare new variables with the
|
||||
same name as a previous variable, and the new variable *shadows* the previous
|
||||
variable. We say that the first variable is *shadowed* by the second, which means
|
||||
that the second variable's value is what you will see when you use the variable.
|
||||
We can shadow a variable by using the same variable's name and repeating the use
|
||||
that the second variable’s value is what you will see when you use the variable.
|
||||
We can shadow a variable by using the same variable’s name and repeating the use
|
||||
of the `let` keyword as follows:
|
||||
|
||||
Filename: src/main.rs
|
||||
@ -174,11 +174,11 @@ The value of x is: 12
|
||||
```
|
||||
|
||||
This is different from marking a variable as `mut` because unless we use the
|
||||
`let` keyword again, we'll get a compile-time error if we accidentally try to
|
||||
`let` keyword again, we’ll get a compile-time error if we accidentally try to
|
||||
reassign to this variable. We can perform a few transformations on a value, but
|
||||
have the variable be immutable after those transformations have been completed.
|
||||
|
||||
The other difference between `mut` and shadowing is that, since we're
|
||||
The other difference between `mut` and shadowing is that, since we’re
|
||||
effectively creating a new variable when we use the `let` keyword again, we can
|
||||
change the type of the value, but reuse the same name. For
|
||||
example, say we ask a user to show us how many spaces they want between some
|
||||
@ -202,7 +202,7 @@ spaces = spaces.len();
|
||||
```
|
||||
|
||||
We will get a compile-time error because we are not allowed to mutate a
|
||||
variable's type:
|
||||
variable’s type:
|
||||
|
||||
```bash
|
||||
error: mismatched types [--explain E0308]
|
||||
@ -216,5 +216,5 @@ note: found type `usize`
|
||||
error: aborting due to previous error
|
||||
```
|
||||
|
||||
Now that we've explored how variables work, let's look at some more
|
||||
Now that we’ve explored how variables work, let’s look at some more
|
||||
data types they can have.
|
||||
|
@ -1,7 +1,7 @@
|
||||
## Data Types
|
||||
|
||||
Every value in Rust is of a certain *type*, which tells Rust what kind of data
|
||||
is being given so it knows how to work with that data. In this section, we'll
|
||||
is being given so it knows how to work with that data. In this section, we’ll
|
||||
look at a number of types built into the language itself. We split the types
|
||||
into two subsets: scalar and compound.
|
||||
|
||||
@ -16,7 +16,7 @@ add a type annotation, like this:
|
||||
let guess: u32 = "42".parse().unwrap();
|
||||
```
|
||||
|
||||
If we don't put the type annotation here, Rust will give us this error that
|
||||
If we don’t put the type annotation here, Rust will give us this error that
|
||||
means the compiler needs more information from us to know which possible type
|
||||
we want:
|
||||
|
||||
@ -35,14 +35,14 @@ You will see some type annotations as we discuss the various data types.
|
||||
|
||||
A *scalar* type represents a single value. There are four primary scalar
|
||||
types in Rust: integers, floating point numbers, booleans, and characters.
|
||||
You'll likely recognize these from other programming languages, but let's jump
|
||||
You’ll likely recognize these from other programming languages, but let’s jump
|
||||
into how they work in Rust.
|
||||
|
||||
#### Integer Types
|
||||
|
||||
An *integer* is a number without a fractional component. We've used one integer
|
||||
An *integer* is a number without a fractional component. We’ve used one integer
|
||||
type already in this chapter, the `i32` type. This type declaration indicates
|
||||
that the value it's associated with should be a signed integer (hence the `i`,
|
||||
that the value it’s associated with should be a signed integer (hence the `i`,
|
||||
as opposed to a `u` for unsigned) for a 32-bit system. There are a number of
|
||||
built-in integer types in Rust, shown in Table 3-1.
|
||||
|
||||
@ -62,11 +62,11 @@ Each variant can be either signed or unsigned and has an explicit size. Signed
|
||||
and unsigned merely refers to whether it is possible for the number to be
|
||||
either negative or positive; in other words, whether the number needs to have a
|
||||
sign with it (signed), or whether it will only ever be positive and can
|
||||
therefore be represented without a sign (unsigned). It's like writing numbers
|
||||
therefore be represented without a sign (unsigned). It’s like writing numbers
|
||||
on paper: when the sign matters, a number is shown with a plus sign or minus
|
||||
sign, but when it's safe to assume the number is positive, it's shown with no
|
||||
sign, but when it’s safe to assume the number is positive, it’s shown with no
|
||||
sign. Signed numbers are stored using two’s complement representation (if
|
||||
you're unsure what this is you can search for it online; an explanation is
|
||||
you’re unsure what this is you can search for it online; an explanation is
|
||||
outside the scope of this text).
|
||||
|
||||
Each signed variant can store numbers from -(2<sup>n - 1</sup>) to 2<sup>n -
|
||||
@ -76,7 +76,7 @@ to 127. Unsigned variants can store numbers from 0 to 2<sup>n</sup> - 1, so a
|
||||
`u8` can store from 0 to 2<sup>8</sup> - 1, which equals 0 to 255.
|
||||
|
||||
Finally, the `isize` and `usize` types depend on the kind of computer your
|
||||
program is running on: 64-bits if you're on a 64-bit architecture, and 32-bits
|
||||
program is running on: 64-bits if you’re on a 64-bit architecture, and 32-bits
|
||||
if you’re on a 32-bit architecture.
|
||||
|
||||
You can write integer literals in any of the forms shown in Table 3-2. Note that
|
||||
@ -93,15 +93,15 @@ all number literals except for the byte literal allow a type suffix, such as
|
||||
|
||||
*Table 3-2: Integer literals in Rust.*
|
||||
|
||||
So how do you know which type of integer to use? If you're unsure, Rust's
|
||||
So how do you know which type of integer to use? If you’re unsure, Rust’s
|
||||
defaults are generally good choices, and integer types default to `i32`: it’s
|
||||
generally the fastest, even on 64-bit systems. The primary situation in which
|
||||
you'd use `isize` or `usize` is when indexing some sort of collection.
|
||||
you’d use `isize` or `usize` is when indexing some sort of collection.
|
||||
|
||||
#### Floating-Point Types
|
||||
|
||||
Rust also has two primitive types for *floating-point numbers*, which are
|
||||
numbers with decimal points. Rust's floating-point types are `f32` and `f64`,
|
||||
numbers with decimal points. Rust’s floating-point types are `f32` and `f64`,
|
||||
which are 32 bits and 64 bits in size, respectively. The default type is `f64`,
|
||||
as it’s roughly the same speed as `f32`, but has a larger precision. It is
|
||||
possible to use an `f64` on 32 bit systems, but it will be slower than using an
|
||||
@ -110,7 +110,7 @@ for better precision is a reasonable initial choice, and you should benchmark
|
||||
your code if you suspect floating-point size is a problem in your case. See
|
||||
Chapter XX for how to run benchmarks.
|
||||
|
||||
Here's an example showing floating-point numbers in action:
|
||||
Here’s an example showing floating-point numbers in action:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
@ -129,7 +129,7 @@ Floating-point numbers are represented according to the IEEE-754 standard. The
|
||||
|
||||
Rust supports the usual basic mathematic operations you’d expect for all of
|
||||
these number types: addition, subtraction, multiplication, division, and
|
||||
remainder. This code shows how you'd use each one in a `let` statement:
|
||||
remainder. This code shows how you’d use each one in a `let` statement:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
@ -173,13 +173,13 @@ fn main() {
|
||||
```
|
||||
|
||||
The main way to consume boolean values is through conditionals like an `if`
|
||||
statement. We’ll cover how `if` statements work in Rust in the "Control Flow"
|
||||
statement. We’ll cover how `if` statements work in Rust in the “Control Flow”
|
||||
section of this chapter.
|
||||
|
||||
#### The Character Type
|
||||
|
||||
So far we’ve only worked with numbers, but Rust supports letters too. Rust’s
|
||||
`char` type is the language's most primitive alphabetic type, and this code
|
||||
`char` type is the language’s most primitive alphabetic type, and this code
|
||||
shows one way to use it:
|
||||
|
||||
Filename: src/main.rs
|
||||
@ -196,9 +196,9 @@ Rust’s `char` represents a Unicode Scalar Value, which means that it can
|
||||
represent a lot more than just ASCII. Accented letters, Chinese/Japanese/Korean
|
||||
ideographs, emoji, and zero width spaces are all valid `char`s in Rust. Unicode
|
||||
Scalar Values range from `U+0000` to `U+D7FF` and `U+E000` to `U+10FFFF`
|
||||
inclusive. A "character" isn’t really a concept in Unicode, however, so your
|
||||
human intuition for what a "character" is may not match up with what a `char`
|
||||
is in Rust. We'll discuss this in detail in the Strings section of Chapter 8.
|
||||
inclusive. A “character” isn’t really a concept in Unicode, however, so your
|
||||
human intuition for what a “character” is may not match up with what a `char`
|
||||
is in Rust. We’ll discuss this in detail in the Strings section of Chapter 8.
|
||||
|
||||
### Compound Types
|
||||
|
||||
@ -212,7 +212,7 @@ distinct types into one compound type.
|
||||
|
||||
We create a tuple by writing a comma-separated list of values inside
|
||||
parentheses. Each position in the tuple has a distinct type, and the types of
|
||||
the different values in the tuple do not have to be the same. We've added
|
||||
the different values in the tuple do not have to be the same. We’ve added
|
||||
optional type annotations in this example:
|
||||
|
||||
Filename: src/main.rs
|
||||
@ -288,14 +288,14 @@ fn main() {
|
||||
```
|
||||
|
||||
While arrays can be useful since they are a primitive type so using them can be
|
||||
very fast, they aren't as flexible as the vector type. The vector type is a
|
||||
very fast, they aren’t as flexible as the vector type. The vector type is a
|
||||
similar collection type provided by the standard library that *is* allowed to
|
||||
grow or shrink in size. If you're unsure whether to use an array or a vector,
|
||||
you should probably go with a vector, and we'll discuss them in more detail in
|
||||
grow or shrink in size. If you’re unsure whether to use an array or a vector,
|
||||
you should probably go with a vector, and we’ll discuss them in more detail in
|
||||
Chapter 8.
|
||||
|
||||
An example of when we might want to use an array is storing the months of the
|
||||
year. It's very unlikely that our program will need to add or remove months, so
|
||||
year. It’s very unlikely that our program will need to add or remove months, so
|
||||
we can use an array since we know we will always have 12 items:
|
||||
|
||||
```rust
|
||||
@ -353,14 +353,14 @@ error: Process didn't exit successfully: `target/debug/arrays` (exit code: 101)
|
||||
```
|
||||
|
||||
We can see that the compilation did not give us any errors, but we got a
|
||||
*runtime* error and our program didn't exit successfully. When we attempt to
|
||||
access an element using indexing, Rust will check that the index we've
|
||||
*runtime* error and our program didn’t exit successfully. When we attempt to
|
||||
access an element using indexing, Rust will check that the index we’ve
|
||||
specified is less than the array length. If the index is greater than the
|
||||
length, it will "panic", which is what it's called when a Rust program exits
|
||||
length, it will *panic*, which is what it’s called when a Rust program exits
|
||||
with an error.
|
||||
|
||||
This is our first example of Rust’s safety principles in action. In many
|
||||
low-level languages, this kind of check is not done, and when you provide an
|
||||
incorrect index, invalid memory can be accessed. Rust protects us against this
|
||||
kind of error by immediately exiting instead of allowing the memory access and
|
||||
continuing. We'll discuss more of Rust’s error handling in Chapter XX.
|
||||
continuing. We’ll discuss more of Rust’s error handling in Chapter XX.
|
||||
|
@ -2,12 +2,12 @@
|
||||
|
||||
Functions are pervasive in Rust code. We’ve already seen one of the most
|
||||
important functions in the language: the `main` function that’s the entry
|
||||
point of many programs. We've also seen the `fn` keyword, which allows us to
|
||||
point of many programs. We’ve also seen the `fn` keyword, which allows us to
|
||||
declare new functions.
|
||||
|
||||
Rust code uses *snake case* as the conventional style for function and variable
|
||||
names. In snake case, all letters are lower case, and there are underscores
|
||||
separating words. Here's a program containing an example function definition:
|
||||
separating words. Here’s a program containing an example function definition:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
@ -108,7 +108,7 @@ type, but they just happen to be in this example. Our function then prints out
|
||||
the values of both of its arguments.
|
||||
|
||||
Let’s try out this code. Replace the program currently in your `function`
|
||||
project's `main.rs` file with the example above, and run it as follows:
|
||||
project’s `main.rs` file with the example above, and run it as follows:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
@ -124,18 +124,18 @@ the two strings are printed with these values.
|
||||
### Function Bodies
|
||||
|
||||
Function bodies are made up of a series of statements optionally ending in an
|
||||
expression. So far, we've only seen functions without an ending expression, but
|
||||
expression. So far, we’ve only seen functions without an ending expression, but
|
||||
we have seen expressions as parts of statements. Since Rust is an
|
||||
expression-based language, this is an important distinction to understand.
|
||||
Other languages don't have the same distinctions, so let's look at what
|
||||
Other languages don’t have the same distinctions, so let’s look at what
|
||||
statements and expressions are and how their differences affect the bodies of
|
||||
functions.
|
||||
|
||||
#### Statements and Expressions
|
||||
|
||||
We've already been using both statements and expressions. *Statements* are
|
||||
We’ve already been using both statements and expressions. *Statements* are
|
||||
instructions that perform some action and do not return a value. *Expressions*
|
||||
evaluate to a resulting value. Let's look at some examples.
|
||||
evaluate to a resulting value. Let’s look at some examples.
|
||||
|
||||
Creating a variable and assigning a value to it with the `let` keyword
|
||||
is a statement. In this example, `let y = 6;` is a statement:
|
||||
@ -177,7 +177,7 @@ error: aborting due to previous error
|
||||
error: Could not compile `functions`.
|
||||
```
|
||||
|
||||
The `let y = 6` statement does not return a value, so there isn't anything for
|
||||
The `let y = 6` statement does not return a value, so there isn’t anything for
|
||||
`x` to bind to. This is different than in other languages like C and Ruby where
|
||||
the assignment returns the value of the assignment. In those languages, we can
|
||||
write `x = y = 6` and have both `x` and `y` have the value `6`; that is not the
|
||||
@ -213,7 +213,7 @@ fn main() {
|
||||
```
|
||||
<!-- If we use wingding numbers to call out code, we might delete the
|
||||
repetition here and just use those numbers--that can help the flow of the text.
|
||||
I'm flagging this as a reminder for when we transfer to libreoffice -->
|
||||
I’m flagging this as a reminder for when we transfer to libreoffice -->
|
||||
|
||||
The expression:
|
||||
|
||||
@ -228,7 +228,7 @@ is a block that, in this case, evaluates to `4`, and then gets bound to
|
||||
`y` as part of the `let` statement.
|
||||
|
||||
Note that the line containing `x + 1` does not have a semicolon at the end,
|
||||
unlike most of the lines we've seen up until now. This is the most important
|
||||
unlike most of the lines we’ve seen up until now. This is the most important
|
||||
distinction between expressions and statements to remember: statements end in
|
||||
semicolons while expressions do not. If you add a semicolon to the end of an
|
||||
expression, that will turn it into a statement, which will then not return a
|
||||
@ -238,8 +238,8 @@ value. Keep this in mind as we explore function return values and expressions.
|
||||
|
||||
Functions can return values back to the code that calls them. We don’t name
|
||||
return values, but we do declare their type, after an arrow (`->`). In Rust,
|
||||
the "return value of the function” is synonymous with the "value of the final
|
||||
expression in the block of the body of a function.” Here's an example of a
|
||||
the “return value of the function” is synonymous with the “value of the final
|
||||
expression in the block of the body of a function.” Here’s an example of a
|
||||
function that returns a value:
|
||||
|
||||
Filename: src/main.rs
|
||||
@ -257,8 +257,8 @@ fn main() {
|
||||
```
|
||||
|
||||
There are no function calls, macros, or even `let` statements in the `five`
|
||||
function: just the number `5` by itself. That's a perfectly valid function in
|
||||
Rust. Note the function's return type is specified, too, as `-> i32`. Try
|
||||
function: just the number `5` by itself. That’s a perfectly valid function in
|
||||
Rust. Note the function’s return type is specified, too, as `-> i32`. Try
|
||||
running this code, and the output should look like this:
|
||||
|
||||
```bash
|
||||
@ -268,7 +268,7 @@ $ cargo run
|
||||
The value of x is: 5
|
||||
```
|
||||
|
||||
The `5` in `five` is the function's return value, which is why the return type
|
||||
The `5` in `five` is the function’s return value, which is why the return type
|
||||
is `i32`. Let’s examine this in more detail. There are two important bits.
|
||||
First, the line `let x = five();` shows us using the return value of a function
|
||||
to initialize a variable.
|
||||
@ -282,7 +282,7 @@ let x = 5;
|
||||
The second interesting bit is the `five` function itself. It requires no
|
||||
arguments and defines the type of the return value, but the body of the
|
||||
function is a lonely `5` with no semicolon because it is an expression whose
|
||||
value we want to return. Let's look at another example:
|
||||
value we want to return. Let’s look at another example:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
@ -334,7 +334,7 @@ error: aborting due to previous error
|
||||
error: Could not compile `functions`.
|
||||
```
|
||||
|
||||
The main error message, "not all control paths return a value", reveals the
|
||||
The main error message, “not all control paths return a value”, reveals the
|
||||
core of the issue with this code. The definition of the function `plus_one`
|
||||
says that it will return an `i32`, but statements don’t evaluate to a value.
|
||||
Therefore, nothing is returned, which contradicts the function definition and
|
||||
|
@ -12,7 +12,7 @@ Here’s a simple comment:
|
||||
```
|
||||
|
||||
In Rust, comments must start with two slashes and will last until the end of
|
||||
the line. For comments that extend beyond a single line, you'll need to
|
||||
the line. For comments that extend beyond a single line, you’ll need to
|
||||
include `//` on each line, like this:
|
||||
|
||||
```rust
|
||||
|
@ -9,8 +9,8 @@ and loops.
|
||||
### `if` Expressions
|
||||
|
||||
An `if` expression allows us to branch our code depending on conditions. We
|
||||
provide a condition and then say, "If this condition is met, run this
|
||||
block of code. If the condition is not met, do not run this block of code."
|
||||
provide a condition and then say, “If this condition is met, run this
|
||||
block of code. If the condition is not met, do not run this block of code.”
|
||||
|
||||
Let’s make a new project to explore `if`, called `branches`. In `src/main.rs`,
|
||||
put:
|
||||
@ -36,7 +36,7 @@ if the condition is true goes immediately after the condition, inside curly
|
||||
braces. These blocks are sometimes called *arms*. We can optionally also
|
||||
include an `else` expression, which we have chosen to do here. This gives the
|
||||
program an alternative block of code to execute should the condition evaluate
|
||||
to false. If you don't give an `else` expression and the condition is false,
|
||||
to false. If you don’t give an `else` expression and the condition is false,
|
||||
the program will just skip the `if` block and move on to the next bit of code.
|
||||
|
||||
Try running this code, and you should see output like this:
|
||||
@ -65,7 +65,7 @@ condition was false
|
||||
```
|
||||
|
||||
It’s also worth noting that the condition here *must* be a `bool`. To see what
|
||||
happens if the condition isn't a `bool`, try running this code:
|
||||
happens if the condition isn’t a `bool`, try running this code:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
@ -153,12 +153,12 @@ When this program executes, it will check each `if` expression in turn and
|
||||
execute the first body for which the condition holds true. Note that even
|
||||
though 6 is divisible by 2, we did not see the output `number is divisible by
|
||||
2`, nor did we see the `number is not divisible by 4, 3, or 2` text from the
|
||||
`else` block. That's because Rust will only execute the block for the first
|
||||
true condition, and once it finds one, it won't even check the rest.
|
||||
`else` block. That’s because Rust will only execute the block for the first
|
||||
true condition, and once it finds one, it won’t even check the rest.
|
||||
|
||||
Using too many `else if` expressions can clutter your code, so if you find
|
||||
yourself with more than one, you may want to look at refactoring your code. In
|
||||
Chapter 6, we'll talk about a powerful Rust branching construct called `match`
|
||||
Chapter 6, we’ll talk about a powerful Rust branching construct called `match`
|
||||
for these cases.
|
||||
|
||||
#### Using `if` in a `let` statement
|
||||
@ -239,7 +239,7 @@ The expression in the `if` block evaluates to an integer and the expression in
|
||||
the `else` block evaluates to a string. This can’t work, because variables
|
||||
must have a single type. Rust needs to know at compile time what type
|
||||
the `number` variable is, definitively, so that it can verify at compile time
|
||||
that its type is valid everywhere we use `number`. Rust wouldn't be able to do
|
||||
that its type is valid everywhere we use `number`. Rust wouldn’t be able to do
|
||||
that if the type of `number` was only determined at runtime; the compiler would
|
||||
be more complex and be able to make fewer guarantees about our code if it had
|
||||
to keep track of multiple hypothetical types for any variable.
|
||||
@ -287,7 +287,7 @@ again!
|
||||
^Cagain!
|
||||
```
|
||||
|
||||
That `^C` there is where we hit `control-c`. You may or may not see "again!"
|
||||
That `^C` there is where we hit `control-c`. You may or may not see `again!`
|
||||
printed after the `^C`, depending on where the code was in the loop when it
|
||||
received the signal to halt.
|
||||
|
||||
@ -299,14 +299,14 @@ correctly.
|
||||
|
||||
#### Conditional Loops With `while`
|
||||
|
||||
It's often useful for a program to have a condition that can be evaluated
|
||||
It’s often useful for a program to have a condition that can be evaluated
|
||||
within a loop. While the condition is true, the loop runs. When the condition
|
||||
ceases to be true, we call `break`, stopping the loop. This could be
|
||||
implemented with a combination of `loop`, `if`, `else`, and `break`; you could
|
||||
try that now in a program, if you'd like.
|
||||
try that now in a program, if you’d like.
|
||||
|
||||
But this pattern is so common that Rust has a more efficient language construct
|
||||
for it, called a `while` loop. Here's an example using `while`: this program
|
||||
for it, called a `while` loop. Here’s an example using `while`: this program
|
||||
loops three times, counting down each time. Finally, after the loop, it prints
|
||||
another message, then exits:
|
||||
|
||||
@ -327,7 +327,7 @@ fn main() {
|
||||
```
|
||||
|
||||
This gets rid of a lot of nesting that would be necessary if we used `loop`,
|
||||
`if`, `else`, and `break`, and it's more clear. While a condition holds, run
|
||||
`if`, `else`, and `break`, and it’s more clear. While a condition holds, run
|
||||
this code; otherwise, exit the loop.
|
||||
|
||||
#### Looping Through a Collection with `for`
|
||||
@ -350,7 +350,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
Here, we're counting up through the elements in the array. We start at index 0,
|
||||
Here, we’re counting up through the elements in the array. We start at index 0,
|
||||
then loop until we hit the final index of our array (that is, when `index < 5`
|
||||
is no longer true). Running this will print out every element of the array:
|
||||
|
||||
@ -370,7 +370,7 @@ will reach a value of `6` at some point, the loop stops executing before trying
|
||||
to fetch a sixth value from the array.
|
||||
|
||||
This approach is error-prone, though; we could cause our program to panic by
|
||||
getting the index length incorrect. It's also slow, as the compiler needs to
|
||||
getting the index length incorrect. It’s also slow, as the compiler needs to
|
||||
perform the conditional check on every element on every iteration through the
|
||||
loop.
|
||||
|
||||
@ -389,8 +389,8 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
If we run this, we'll see the same output as the previous example. Importantly,
|
||||
though, we've now increased the safety of our code and eliminated the chance of
|
||||
If we run this, we’ll see the same output as the previous example. Importantly,
|
||||
though, we’ve now increased the safety of our code and eliminated the chance of
|
||||
bugs that might result from going beyond the end of the array or not going far
|
||||
enough and missing some items.
|
||||
|
||||
@ -400,7 +400,7 @@ item from the `a` array but forgot to update the condition to `while index <
|
||||
remember to change any other code if we changed the number of values in the
|
||||
array.
|
||||
|
||||
If you're wondering about the `iter` code in this example, keep reading! We
|
||||
If you’re wondering about the `iter` code in this example, keep reading! We
|
||||
will cover method syntax generally in Chapter XX and iterators specifically in
|
||||
Chapter XX.
|
||||
|
||||
@ -411,8 +411,8 @@ Rustaceans would use a `for` loop. The way to do that would be to use a
|
||||
`Range`, which is a type provided by the standard library that generates all
|
||||
numbers in sequence starting from one number and ending before another number.
|
||||
|
||||
Here's what the countdown would look like with a for loop, and using another
|
||||
method we haven't yet talked about, `rev`, to reverse the range:
|
||||
Here’s what the countdown would look like with a for loop, and using another
|
||||
method we haven’t yet talked about, `rev`, to reverse the range:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
@ -425,12 +425,12 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
That's a bit nicer, isn't it?
|
||||
That’s a bit nicer, isn’t it?
|
||||
|
||||
## Summary
|
||||
|
||||
You made it! That was a big chapter: we covered variables, scalar and
|
||||
compound data types, functions, comments, `if` expressions, and loops! If you'd
|
||||
compound data types, functions, comments, `if` expressions, and loops! If you’d
|
||||
like to get some practice with the concepts in this chapter, try building
|
||||
programs to do things like:
|
||||
|
||||
@ -439,5 +439,5 @@ programs to do things like:
|
||||
* Print the lyrics to the Christmas carol *The Twelve Days of Christmas*,
|
||||
taking advantage of the repetition in the song.
|
||||
|
||||
When you're ready to move on, we'll talk about a concept in Rust that *doesn't*
|
||||
When you’re ready to move on, we’ll talk about a concept in Rust that *doesn’t*
|
||||
commonly exist in other programming languages: ownership.
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Understanding Ownership
|
||||
|
||||
Ownership is Rust's most unique feature and enables Rust to make memory safety
|
||||
guarantees without needing a garbage collector. It's therefore important to
|
||||
understand how ownership works in Rust. In this chapter we'll talk about
|
||||
Ownership is Rust’s most unique feature, and enables Rust to make memory safety
|
||||
guarantees without needing a garbage collector. It’s therefore important to
|
||||
understand how ownership works in Rust. In this chapter we’ll talk about
|
||||
ownership as well as several related features: borrowing, slices, and how Rust
|
||||
lays things out in memory.
|
||||
|
@ -3,7 +3,7 @@
|
||||
Rust’s central feature is *ownership*. It is a feature that is straightforward
|
||||
to explain, but has deep implications for the rest of the language.
|
||||
|
||||
All programs have to manage the way they use a computer's memory while running.
|
||||
All programs have to manage the way they use a computer’s memory while running.
|
||||
Some languages have garbage collection, while in others, the programmer has to
|
||||
explicitly allocate and free the memory. Rust takes a third approach: memory is
|
||||
managed through a system of ownership with a set of rules that the compiler
|
||||
@ -12,21 +12,21 @@ features.
|
||||
|
||||
Since ownership is a new concept for many programmers, it does take
|
||||
some time to get used to. There is good news, though: the more experienced you
|
||||
become with Rust and the rules of the ownership system, the more you'll be
|
||||
become with Rust and the rules of the ownership system, the more you’ll be
|
||||
able to naturally develop code that is both safe and efficient. Keep at it!
|
||||
|
||||
Once you understand ownership, you have a good foundation for understanding the
|
||||
features that make Rust unique. In this chapter, we'll learn ownership by going
|
||||
features that make Rust unique. In this chapter, we’ll learn ownership by going
|
||||
through some examples, focusing on a very common data structure: strings.
|
||||
|
||||
<!-- PROD: START BOX -->
|
||||
|
||||
> #### The Stack and the Heap
|
||||
>
|
||||
> In many programming languages, we don't have to think about the stack and the
|
||||
> In many programming languages, we don’t have to think about the stack and the
|
||||
> heap very often. But in a systems programming language like Rust, whether a
|
||||
> value is on the stack or the heap has more of an effect on how the language
|
||||
> behaves and why we have to make certain decisions. We're going to be
|
||||
> behaves and why we have to make certain decisions. We’re going to be
|
||||
> describing parts of ownership in relation to the stack and the heap, so here
|
||||
> is a brief explanation.
|
||||
>
|
||||
@ -36,7 +36,7 @@ through some examples, focusing on a very common data structure: strings.
|
||||
> This is referred to as *last in, first out*. Think of a stack of plates: when
|
||||
> you add more plates, you put them on top of the pile, and when you need a
|
||||
> plate, you take one off the top. Adding or removing plates from the middle or
|
||||
> bottom wouldn't work as well! Adding data is called *pushing onto the stack*
|
||||
> bottom wouldn’t work as well! Adding data is called *pushing onto the stack*
|
||||
> and removing data is called *popping off the stack*.
|
||||
>
|
||||
> The stack is fast because of the way it accesses the data: it never has to
|
||||
@ -49,7 +49,7 @@ through some examples, focusing on a very common data structure: strings.
|
||||
> when we put data on the heap, we ask for some amount of space. The operating
|
||||
> system finds an empty spot somewhere in the heap that is big enough, marks it
|
||||
> as being in use, and returns to us a pointer to that location. This process
|
||||
> is called *allocating on the heap*, and sometimes we just say "allocating"
|
||||
> is called *allocating on the heap*, and sometimes we just say *allocating*
|
||||
> for short. Pushing values onto the stack is not considered allocating. Since
|
||||
> the pointer is a known, fixed size, we can store the pointer on the stack,
|
||||
> but when we want the actual data, we have to follow the pointer.
|
||||
@ -63,14 +63,14 @@ through some examples, focusing on a very common data structure: strings.
|
||||
> get there. Allocating a large amount of space can also take time.
|
||||
>
|
||||
> When our code calls a function, the values passed into the function
|
||||
> (including, potentially, pointers to data on the heap) and the function's
|
||||
> (including, potentially, pointers to data on the heap) and the function’s
|
||||
> local variables get pushed onto the stack. When the function is over, those
|
||||
> values get popped off the stack.
|
||||
>
|
||||
> Keeping track of what parts of code are using what data on the heap,
|
||||
> minimizing the amount of duplicate data on the heap, and cleaning up unused
|
||||
> data on the heap so that we don't run out of space are all problems that
|
||||
> ownership addresses. Once you understand ownership, you won't need to think
|
||||
> data on the heap so that we don’t run out of space are all problems that
|
||||
> ownership addresses. Once you understand ownership, you won’t need to think
|
||||
> about the stack and the heap very often, but knowing that managing heap data
|
||||
> is why ownership exists can help explain why it works the way it does.
|
||||
|
||||
@ -78,7 +78,7 @@ through some examples, focusing on a very common data structure: strings.
|
||||
|
||||
### Ownership Rules
|
||||
|
||||
First, let's take a look at the rules. Keep these in mind as we go through the
|
||||
First, let’s take a look at the rules. Keep these in mind as we go through the
|
||||
examples that will illustrate the rules:
|
||||
|
||||
> 1. Each value in Rust has a variable that’s called its *owner*.
|
||||
@ -87,16 +87,16 @@ examples that will illustrate the rules:
|
||||
|
||||
### Variable Scope
|
||||
|
||||
We've walked through an example of a Rust program already in the tutorial
|
||||
We’ve walked through an example of a Rust program already in the tutorial
|
||||
chapter. Now that we’re past basic syntax, we won’t include all of the `fn
|
||||
main() {` stuff in examples, so if you’re following along, you will have to put
|
||||
the following examples inside of a `main` function yourself. This lets our
|
||||
examples be a bit more concise, letting us focus on the actual details rather
|
||||
than boilerplate.
|
||||
|
||||
As a first example of ownership, we'll look at the *scope* of some variables.
|
||||
As a first example of ownership, we’ll look at the *scope* of some variables.
|
||||
A scope is the range within a program for which an item is valid.
|
||||
Let's say we have a variable that looks like this:
|
||||
Let’s say we have a variable that looks like this:
|
||||
|
||||
```rust
|
||||
let s = "hello";
|
||||
@ -125,17 +125,17 @@ build on top of this understanding by introducing the `String` type.
|
||||
### The `String` Type
|
||||
|
||||
In order to illustrate the rules of ownership, we need a data type that is more
|
||||
complex than the ones we covered in Chapter 3. All of the data types we've
|
||||
complex than the ones we covered in Chapter 3. All of the data types we’ve
|
||||
looked at previously are stored on the stack and popped off the stack when
|
||||
their scope is over, but we want to look at data that is stored on the heap and
|
||||
explore how Rust knows when to clean that data up.
|
||||
|
||||
We're going to use `String` as the example here and concentrate on the parts of
|
||||
We’re going to use `String` as the example here and concentrate on the parts of
|
||||
`String` that relate to ownership. These aspects also apply to other complex
|
||||
data types provided by the standard library and that you create. We'll go into
|
||||
data types provided by the standard library and that you create. We’ll go into
|
||||
more depth about `String` specifically in Chapter XX.
|
||||
|
||||
We've already seen string literals, where a string value is hard-coded into our
|
||||
We’ve already seen string literals, where a string value is hard-coded into our
|
||||
program. String literals are convenient, but they aren’t always suitable for
|
||||
every situation you want to use text. For one thing, they’re immutable. For
|
||||
another, not every string value can be known when we write our code: what if we
|
||||
@ -189,9 +189,9 @@ implementation requests the memory it needs. This is pretty much universal in
|
||||
programming languages.
|
||||
|
||||
The second case, however, is different. In languages with a *garbage collector*
|
||||
(GC), the GC will keep track and clean up memory that isn't being used anymore,
|
||||
(GC), the GC will keep track and clean up memory that isn’t being used anymore,
|
||||
and we, as the programmer, don’t need to think about it. Without GC, it’s the
|
||||
programmer's responsibility to identify when memory is no longer being used and
|
||||
programmer’s responsibility to identify when memory is no longer being used and
|
||||
call code to explicitly return it, just as we did to request it. Doing this
|
||||
correctly has historically been a difficult problem in programming. If we
|
||||
forget, we will waste memory. If we do it too early, we will have an invalid
|
||||
@ -230,7 +230,7 @@ allocated on the heap. Let’s go over some of those situations now.
|
||||
#### Ways Variables and Data Interact: Move
|
||||
|
||||
There are different ways that multiple variables can interact with the same data
|
||||
in Rust. Let's take an example using an integer:
|
||||
in Rust. Let’s take an example using an integer:
|
||||
|
||||
```rust
|
||||
let x = 5;
|
||||
@ -252,7 +252,7 @@ let s2 = s1;
|
||||
|
||||
This looks very similar to the previous code, so we might assume that the way
|
||||
it works would be the same: that the second line would make a copy of the value
|
||||
in `s1` and bind it to `s2`. This isn't quite what happens.
|
||||
in `s1` and bind it to `s2`. This isn’t quite what happens.
|
||||
|
||||
To explain this more thoroughly, let’s take a look at what `String` looks like
|
||||
under the covers in Figure 4-1. A `String` is made up of three parts, shown on
|
||||
@ -262,24 +262,24 @@ is the memory that holds the contents, and this is on the heap.
|
||||
|
||||
<img alt="String in memory" src="img/trpl04-01.svg" class="center" style="width: 50%;" />
|
||||
|
||||
Figure 4-1: Representation in memory of a `String` holding the value "hello"
|
||||
Figure 4-1: Representation in memory of a `String` holding the value `"hello"`
|
||||
bound to `s1`
|
||||
|
||||
The length is how much memory, in bytes, the contents of the `String` is
|
||||
currently using. The capacity is the total amount of memory, in bytes, that the
|
||||
`String` has gotten from the operating system. The difference between length
|
||||
and capacity matters but not in this context, so for now, it's fine to ignore
|
||||
and capacity matters but not in this context, so for now, it’s fine to ignore
|
||||
the capacity.
|
||||
|
||||
When we assign `s1` to `s2`, the `String` data itself is copied, meaning we
|
||||
copy the pointer, the length, and the capacity that are on the stack. We do not
|
||||
copy the data on the heap that the `String`'s pointer refers to. In other
|
||||
copy the data on the heap that the `String`’s pointer refers to. In other
|
||||
words, it looks like figure 4-2.
|
||||
|
||||
<img alt="s1 and s2 pointing to the same value" src="img/trpl04-02.svg" class="center" style="width: 50%;" />
|
||||
|
||||
Figure 4-2: Representation in memory of the variable `s2` that has a copy of
|
||||
`s1`'s pointer, length and capacity
|
||||
`s1`’s pointer, length and capacity
|
||||
|
||||
And *not* Figure 4-3, which is what memory would look like if Rust instead
|
||||
copied the heap data as well. If Rust did this, the operation `s2 = s1` could
|
||||
@ -298,7 +298,7 @@ same memory. This is known as a *double free* error and is one of the memory
|
||||
safety bugs we mentioned before. Freeing memory twice can lead to memory
|
||||
corruption, which can potentially lead to security vulnerabilities.
|
||||
|
||||
In order to ensure memory safety, there's one more detail to what happens in
|
||||
In order to ensure memory safety, there’s one more detail to what happens in
|
||||
this situation in Rust. Instead of trying to copy the allocated memory, Rust
|
||||
says that `s1` is no longer valid and, therefore, doesn’t need to free anything
|
||||
when it goes out of scope. Check out what happens when you try to use `s1`
|
||||
@ -323,11 +323,11 @@ println!("{}", s1);
|
||||
^~
|
||||
```
|
||||
|
||||
If you have heard the terms "shallow copy" and "deep copy" while working with
|
||||
If you have heard the terms “shallow copy” and “deep copy” while working with
|
||||
other languages, the concept of copying the pointer, length, and capacity
|
||||
without copying the data probably sounds like a shallow copy. But because Rust
|
||||
also invalidates the first variable, instead of calling this a shallow copy,
|
||||
it's known as a *move*. Here we would read this by saying that `s1` was *moved*
|
||||
it’s known as a *move*. Here we would read this by saying that `s1` was *moved*
|
||||
into `s2`. So what actually happens looks like Figure 4-4.
|
||||
|
||||
<img alt="s1 moved to s2" src="img/trpl04-04.svg" class="center" style="width: 50%;" />
|
||||
@ -338,7 +338,7 @@ That solves our problem! With only `s2` valid, when it goes out of scope, it
|
||||
alone will free the memory, and we’re done.
|
||||
|
||||
Furthermore, there’s a design choice that’s implied by this: Rust will never
|
||||
automatically create "deep" copies of your data. Therefore, any *automatic*
|
||||
automatically create “deep” copies of your data. Therefore, any *automatic*
|
||||
copying can be assumed to be inexpensive.
|
||||
|
||||
#### Ways Variables and Data Interact: Clone
|
||||
@ -379,18 +379,18 @@ let y = x;
|
||||
println!("x = {}, y = {}", x, y);
|
||||
```
|
||||
|
||||
This seems to contradict what we just learned: we don't have a call to
|
||||
`clone`, but `x` is still valid, and wasn't moved into `y`.
|
||||
This seems to contradict what we just learned: we don’t have a call to `clone`,
|
||||
but `x` is still valid, and wasn’t moved into `y`.
|
||||
|
||||
This is because types like integers that have a known size at compile time are
|
||||
stored entirely on the stack, so copies of the actual values are quick to make.
|
||||
That means there's no reason we would want to prevent `x` from being valid
|
||||
That means there’s no reason we would want to prevent `x` from being valid
|
||||
after we create the variable `y`. In other words, there’s no difference between
|
||||
deep and shallow copying here, so calling `clone` wouldn’t do anything
|
||||
differently from the usual shallow copying and we can leave it out.
|
||||
|
||||
Rust has a special annotation called the `Copy` trait that we can place on
|
||||
types like these (we'll talk more about traits in Chapter XX). If a type has
|
||||
types like these (we’ll talk more about traits in Chapter XX). If a type has
|
||||
the `Copy` trait, an older variable is still usable after assignment. Rust will
|
||||
not let us annotate a type with the `Copy` trait if the type, or any of its
|
||||
parts, has implemented `drop`. If the type needs something special to happen
|
||||
@ -421,7 +421,7 @@ Filename: src/main.rs
|
||||
fn main() {
|
||||
let s = String::from("hello"); // s comes into scope.
|
||||
|
||||
takes_ownership(s); // s's value moves into the function...
|
||||
takes_ownership(s); // s’s value moves into the function...
|
||||
// ... and so is no longer valid here.
|
||||
let x = 5; // x comes into scope.
|
||||
|
||||
@ -429,7 +429,7 @@ fn main() {
|
||||
// but i32 is Copy, so it’s okay to still
|
||||
// use x afterward.
|
||||
|
||||
} // Here, x goes out of scope, then s. But since s's value was moved, nothing
|
||||
} // Here, x goes out of scope, then s. But since s’s value was moved, nothing
|
||||
// special happens.
|
||||
|
||||
fn takes_ownership(some_string: String) { // some_string comes into scope.
|
||||
@ -449,7 +449,8 @@ and where the ownership rules prevent you from doing so.
|
||||
|
||||
### Return Values and Scope
|
||||
|
||||
Returning values can also transfer ownership. Here's an example with similar annotations:
|
||||
Returning values can also transfer ownership. Here’s an example with similar
|
||||
annotations:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
@ -485,7 +486,7 @@ fn takes_and_gives_back(a_string: String) -> String { // a_string comes into sco
|
||||
```
|
||||
|
||||
It’s the same pattern, every time: assigning a value to another variable moves
|
||||
it, and when heap data values' variables go out of scope, if the data hasn’t
|
||||
it, and when heap data values’ variables go out of scope, if the data hasn’t
|
||||
been moved to be owned by another variable, the value will be cleaned up by
|
||||
`drop`.
|
||||
|
||||
|
@ -63,7 +63,7 @@ fn calculate_length(s: &String) -> usize { // s is a reference to a String
|
||||
```
|
||||
|
||||
It’s the same process as before, but we don’t drop what the reference points to
|
||||
when it goes out of scope because we don't have ownership. This lets us write
|
||||
when it goes out of scope because we don’t have ownership. This lets us write
|
||||
functions which take references as arguments instead of the values themselves,
|
||||
so that we won’t need to return them to give back ownership.
|
||||
|
||||
@ -71,7 +71,7 @@ We call this process *borrowing*. Just like with real life, if a person owns
|
||||
something, you can borrow it from them, and when you’re done, you have to give
|
||||
it back.
|
||||
|
||||
So what happens if we try to modify something we're borrowing? Try this code
|
||||
So what happens if we try to modify something we’re borrowing? Try this code
|
||||
out. Spoiler alert: it doesn’t work!
|
||||
|
||||
Filename: src/main.rs
|
||||
@ -156,10 +156,10 @@ mutate whenever you’d like. The benefit of having this restriction is that Rus
|
||||
can prevent data races at compile time. A *data race* is a particular type of
|
||||
race condition where two or more pointers access the same data at the same
|
||||
time, at least one of the pointers is being used to write to the data, and
|
||||
there's no mechanism being used to synchronize access to the data. Data races
|
||||
there’s no mechanism being used to synchronize access to the data. Data races
|
||||
cause undefined behavior and can be difficult to diagnose and fix when trying
|
||||
to track them down at runtime; Rust prevents this problem from happening since
|
||||
it won't even compile code with data races!
|
||||
it won’t even compile code with data races!
|
||||
|
||||
As always, we can use `{}`s to create a new scope, allowing for multiple mutable
|
||||
references, just not *simultaneous* ones:
|
||||
@ -204,17 +204,17 @@ error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immuta
|
||||
Whew! We *also* cannot have a mutable reference while we have an immutable one.
|
||||
Users of an immutable reference don’t expect the values to suddenly change out
|
||||
from under them! Multiple immutable references are okay, however, since no one
|
||||
who is just reading the data has the ability to affect anyone else's reading of
|
||||
who is just reading the data has the ability to affect anyone else’s reading of
|
||||
the data.
|
||||
|
||||
Even though these errors may be frustrating at times, remember that it's the
|
||||
Even though these errors may be frustrating at times, remember that it’s the
|
||||
Rust compiler pointing out a potential bug earlier (at compile time rather than
|
||||
at runtime) and showing you exactly where the problem is instead of you having
|
||||
to track down why sometimes your data isn't what you thought it should be.
|
||||
to track down why sometimes your data isn’t what you thought it should be.
|
||||
|
||||
### Dangling References
|
||||
|
||||
In languages with pointers, it's easy to make the error of creating a *dangling
|
||||
In languages with pointers, it’s easy to make the error of creating a *dangling
|
||||
pointer*, a pointer referencing a location in memory that may have been given
|
||||
to someone else, by freeing some memory while keeping around a pointer to that
|
||||
memory. In Rust, by contrast, the compiler guarantees that references will
|
||||
@ -255,12 +255,12 @@ error: aborting due to previous error
|
||||
```
|
||||
|
||||
This error message refers to a feature we haven’t learned about yet:
|
||||
*lifetimes*. We'll discuss lifetimes in detail in Chapter XX, but, disregarding
|
||||
*lifetimes*. We’ll discuss lifetimes in detail in Chapter XX, but, disregarding
|
||||
the parts about lifetimes, the message does contain the key to why this code is
|
||||
a problem: `this function’s return type contains a borrowed value, but there is
|
||||
no value for it to be borrowed from`.
|
||||
|
||||
Let’s have a closer look at exactly what's happening at each stage of our
|
||||
Let’s have a closer look at exactly what’s happening at each stage of our
|
||||
`dangle` code:
|
||||
|
||||
```rust,ignore
|
||||
@ -299,4 +299,4 @@ Here’s a recap of what we’ve talked about:
|
||||
2. Any number of immutable references.
|
||||
2. References must always be valid.
|
||||
|
||||
Next, let's look at a different kind of reference: slices.
|
||||
Next, let’s look at a different kind of reference: slices.
|
||||
|
@ -102,13 +102,13 @@ fn main() {
|
||||
|
||||
s.clear(); // This empties the String, making it equal to "".
|
||||
|
||||
// word still has the value 5 here, but there's no more string that
|
||||
// word still has the value 5 here, but there’s no more string that
|
||||
// we could meaningfully use the value 5 with. word is now totally invalid!
|
||||
}
|
||||
```
|
||||
|
||||
This program compiles without any errors, and also would if we used `word`
|
||||
after calling `s.clear()`. `word` isn't connected to the state of `s` at all,
|
||||
after calling `s.clear()`. `word` isn’t connected to the state of `s` at all,
|
||||
so `word` still contains the value `5`. We could use that `5` with `s` to try
|
||||
to extract the first word out, but this would be a bug since the contents of
|
||||
`s` have changed since we saved `5` in `word`.
|
||||
@ -121,7 +121,7 @@ fn second_word(s: &String) -> (usize, usize) {
|
||||
```
|
||||
|
||||
Now we’re tracking both a start *and* an ending index, and we have even more
|
||||
values that were calculated from data in a particular state but aren't tied to
|
||||
values that were calculated from data in a particular state but aren’t tied to
|
||||
that state at all. We now have three unrelated variables floating
|
||||
around which need to be kept in sync.
|
||||
|
||||
@ -190,7 +190,7 @@ let slice = &s[..];
|
||||
```
|
||||
|
||||
With this in mind, let’s re-write `first_word` to return a slice. The type
|
||||
that signifies "string slice" is written as `&str`:
|
||||
that signifies “string slice” is written as `&str`:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
@ -225,7 +225,7 @@ fn second_word(s: &String) -> &str {
|
||||
|
||||
We now have a straightforward API that’s much harder to mess up. Remember our
|
||||
bug from before, when we got the first word but then cleared the string so that
|
||||
our first word was invalid? That code was logically incorrect but didn't show
|
||||
our first word was invalid? That code was logically incorrect but didn’t show
|
||||
any immediate errors. The problems would show up later, if we kept trying to
|
||||
use the first word index with an emptied string. Slices make this bug
|
||||
impossible, and let us know we have a problem with our code much sooner. Using
|
||||
@ -361,9 +361,9 @@ The concepts of ownership, borrowing, and slices are what ensure memory safety
|
||||
in Rust programs at compile time. Rust is a language that gives you control
|
||||
over your memory usage like other systems programming languages, but having the
|
||||
owner of data automatically clean up that data when the owner goes out of scope
|
||||
means you don't have to write and debug extra code to get this control.
|
||||
means you don’t have to write and debug extra code to get this control.
|
||||
|
||||
Ownership affects how lots of other parts of Rust work, so we will be talking
|
||||
about these concepts further throughout the rest of the book. Let's move on to
|
||||
the next chapter where we'll look at grouping pieces of data together in a
|
||||
about these concepts further throughout the rest of the book. Let’s move on to
|
||||
the next chapter where we’ll look at grouping pieces of data together in a
|
||||
`struct`.
|
||||
|
Loading…
Reference in New Issue
Block a user