Rust学习——TRPL-Part4

Common Programming Concepts

Variables and Mutability

By deault, variables are immutable.

In Rust, the compiler guarantees that when you state that a value won’t change, it really won’t change.

Mutablitiy can be very useful. Just add mut in front of the variable name.

Mutable / Immutable Trade-off

There are multiple trade-offs to consider in addition to the prevention of bugs. For example, in cases where you’re using large data structures, mutating an instance in place may be faster than copying and returning newly allocated instances. With smaller data structures, creating new instances and writing in a more functional programming style may be easier to think through, so lower performance might be a worthwhile penalty for gaining that clarity.

Differences Between Variables and Constants

Yep, 不可以更改变量的值很容易联想起C或其他编程语言中的constants

Difference:

  1. Can't add mut with constants. Not by default but always immutable.
  2. Using constant keyword instead of the let keyword. Type of the value must be annoted.
  3. Constants can be declared in any scope, including the global scope.
  4. Constants may be set only to a constant expression, not the result of a function call or any other value that could only be computed at runtime.

Rust's naming convention for constants

Use all uppercase with underscores between words.

For example:

1
const MAX_POINTS: u32 = 100_000;

Recommended Usage:

  1. For values in your application domain that mutipule parts of the program might need to know about, such as the maximum number of points any player of a game
  2. Naming hardcoded values used throughout your program as constants for conveying the meaning and reusage.

Shadowing

We can shadow a variable by using the same variable’s name and repeating the use of the let keyword.

Difference between shadowing and mutable variables

Variables can still be immutable and we’ll get a compile-time error if we accidentally try to reassign to this variable without using the let keyword. By using let, 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 because 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.

We can compile the following code:

1
2
3
4
fn main() {
let spaces = " ";
let spaces = spaces.len();
}

The first spaces is a string type but the second after let, is a brand-new variable with the same name, is a number type. Shadowing thus spares us from having to come up with different names, such as spaces_str and spaces_num.

However, if we try to use mut for this, we'll get a compiler-time error:

1
2
3
4
fn main() {
let mut spaces = " ";
spaces = spaces.len();
}

Not allowed to mutable a variable's type. We will get mismatched types.

Data Types

Scalar Types

A scalar type represents a single value. Rust has four primary scalar types: integers, floating-point numbers, Booleans, and characters.

Integer Types

Signed integer start with i(from \(0\) to \(2^n-1\)) and unsigned start with u(from \(-(2^{n-1}\) to \(2^{n-1}-1\)).

Can write integer literals in any of the forms shown bellow. Number literals can also use _ as a visual separator to make the number easier to read, such as 1_000.

Number literals example
Decimal 98_222
Hex 0xff
Octal 0o77
Binary 0b1111_0000
Byte(u8 only) b'A'

If you’re unsure, Rust’s defaults are generally good places to start: integer types default to i32. The primary situation in which you’d use isize or usize is when indexing some sort of collection.

Floating-Point Types

Floating-point numbers are represented according to the IEEE-754 standard. The f32 type is a single-precision float, and f64 has double precision.

The Boolean Type

The Character Type

Compound Types

Compound types can group multiple values into one type. Rust has two primitive compound types: tuples and arrays.

The Tuple Type

Each position in the tuple has a type. Don't have to be the same.

1
2
3
4
5
6
7
8
9
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);

let five_hundred = x.0;

let six_point_four = x.1;

let one = x.2;
}

The Array Type

Unlike a tuple, every element of an array must have the same type.

Arrays are useful when you want your data allocated on the stack rather than the heap (we will discuss the stack and the heap more in Chapter 4) or when you want to ensure you always have a fixed number of elements.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
fn main() {
let a = [1, 2, 3, 4, 5];

let first = a[0];
let second = a[1];
}
```

## Functions

Function definitions in Rust start with `fn`.

### Function Parameters

区别一些若类型语言,Rust 要求在function signatures 中给出每一个参数的类型声明。

In function signatures, you must declare the type of each parameter. This is a deliberate decision in Rusts design: requiring type annotations in function definitions means the compiler almost never needs you to use them elsewhere in the code to figure out what you mean.

### Function Bodies Contain Statements and Expressions

- *Statements* are instructions that perform some action and do not return a value.
- *Expressions* evaluate to a resulting value. Lets look at some examples.

Creating a variable and assigning a value to it with the `let` keyword is a statement. And the function definitions are also statements.

```Rust
fn main() {
let y = 6; // this line is a statement
} // the fn def is a statement

We can not write Rust code let x = (let y = 6); (compare to x = y = 6; in C code). The let y = 6 statement does not return a value, so there isn't anything for x to bind to.

Expressions evaluate to something and make up most of the rest of the code that you’ll write in Rust.

Expressions can be part of statements. Single value assign to variable on the right of = is an expression. The block that we use to create new scopes, {}, is an expression. Calling a function is an expression.

Functions with Return Values

Declare their type after an arrow(->). Return value of the function is synonymous with the value of the final expression in the block of the body of a function. Using return keyword and or return the last expression implicitly.

BECAREFUL TO SEMICOLON

注意隐式返回要求是Expression,如果加上分号则认为是Statement,此时默认返回的是 empty tuple (),编译时会发现mismatched types.

1
2
3
4
5
6
7
8
9
10
fn main() {
let x = plus_one(5);

println!("The value of x is: {}", x);
}

fn plus_one(x: i32) -> i32 {
x + 1 // right
// x + 1; // wrong
}

Control Flow

if expressions and loops.

Rust if expressions 要求值类型必须为bool 类似C中的写法会产生错误。

1
2
3
4
5
6
7
fn main() {
let number = 3;

if number {
println!("number was three");
}
}

cargo run得到的报错信息:

1
2
3
4
5
6
7
8
9
10
11
12
error[E0308]: mismatched types
--> src/main.rs:4:8
|
4 | if number {
| ^^^^^^ expected `bool`, found integer

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: could not compile `control_flow`

To learn more, run the command again with --verbose.

The error indicates that Rust expected a bool but got an integer. Unlike languages such as Ruby and JavaScript, Rust will not automatically try to convert non-Boolean types to a Boolean. You must be explicit and always provide if with a Boolean as its condition.

Using if in a let Statement

if 也是 expression 我们可以将其用在let 右侧。

1
2
3
4
5
6
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };

println!("The value of number is: {}", number);
}

Pay attention:

  1. Blocks of code evaluate to last expression in them.
  2. Types need to be matched (If not, we'll get an error).

Repetition with Loops

Rust has three kinds of loops: loop, while, and for.

Returning Values from Loops

One of the uses of a loop is to retry an operation you know might fail, such as checking whether a thread has completed its job. However, you might need to pass the result of that operation to the rest of your code. To do this, you can add the value you want returned after the break expression you use to stop the loop; that value will be returned out of the loop so you can use it, as shown here:

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
let mut counter = 0;

let result = loop {
counter += 1;

if counter == 10 {
break counter * 2;
}
};

println!("The result is {}", result);
}

for loop 是Rustaceans最常用的,使用迭代器,方便快捷,安全。