Rust学习笔记

Rust编程语言入门教程课程笔记

参考教材: The Rust Programming Language (by Steve Klabnik and Carol Nichols, with contributions from the Rust Community)

Lecture 3 Common Programming Concepts

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
fn main() {
// Variables and Mutability
let mut x = 5; // mut means mutable
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");

// Constants
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3; // all caps with underscores
println!("The value of THREE_HOURS_IN_SECONDS is: {THREE_HOURS_IN_SECONDS}");
// Shadowing
let y = 5;
println!("The value of y is: {y}");
let y = y + 1; //shadowing
// this is allowed because we are creating a new variable
println!("The value of y is: {y}");

// Shadowing vs Mutability
let spaces = " ";
let spaces = spaces.len(); // this is allowed because we are creating a new variable
println!("The value of spaces is: {spaces}");

// Data Types
let guess: u32 = "42".parse().expect("Not a number!"); // type annotation
println!("The value of guess is: {guess}");
let m = 57u8; // u8
println!("The value of m is: {m}");
let n = 1_000_000; // i32
println!("The value of n is: {n}");
let x = 2.0; // f64
let y: f32 = 3.0; // f32
println!("The value of x is: {x}");
println!("The value of y is: {y}");
// addition
let sum = 5 + 10;
println!("The value of sum is: {sum}");
// subtraction
let difference = 95.5 - 4.3;
println!("The value of difference is: {difference}");
// multiplication
let product = 4 * 30;
println!("The value of product is: {product}");
// division
let quotient = 56.7 / 32.2;
let truncated = -5 / 3; // Results in -1
println!("The value of quotient is: {quotient}");
println!("The value of truncated is: {truncated}");
// remainder
let remainder = 43 % 5;
println!("The value of remainder is: {remainder}");
// boolean
let t = true;
let f: bool = false; // with explicit type annotation
println!("The value of t is: {t}");
println!("The value of f is: {f}");
// character
let c = 'z';
let z: char = 'ℤ'; // with explicit type annotation
let heart_eyed_cat = '😻';
println!("The value of c is: {c}");
println!("The value of z is: {z}");
println!("The value of heart_eyed_cat is: {heart_eyed_cat}");

// Compound Types

// Tuple
let tup: (i32, f64, u8) = (500, 6.4, 1); // type annotation
println!("The value of tup is: {:?}", tup);
let (x, y, z) = tup; // destructuring
println!("The value of x is: {x}");
println!("The value of y is: {y}");
println!("The value of z is: {z}");
let five_hundred = tup.0; // accessing tuple elements
println!("The value of five_hundred is: {five_hundred}");
let six_point_four = tup.1;
println!("The value of six_point_four is: {six_point_four}");
let one = tup.2;
println!("The value of one is: {one}");

//Array
//saved on stack
// fixed length and same type
let a = [1, 2, 3, 4, 5]; // array
println!("The value of a is: {:?}", a);
let a = [3; 5]; // [3, 3, 3, 3, 3] // [element; size]
let first = a[0];
let second = a[1];
println!("The value of first is: {first}");
println!("The value of second is: {second}");

// Functions
another_function(5);

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

// Control Flow

// if-else flow
let number = 6;
if number % 4 == 0 {
println!("number is divisible by 4");
} else if number % 3 == 0 {
println!("number is divisible by 3");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3, or 2");
}

let condition = true;
let number = if condition { 5 } else { 6 };
println!("The value of number is: {number}");

// loop flow
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // break with value
}// no semicolon here
};// semicolon here
println!("The value of result is: {result}");

// while flow
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}// no semicolon here
println!("LIFTOFF!!!");

// for flow
let a = [10, 20, 30, 40, 50];
for element in a.iter() {// iter() returns each element in a collection
println!("the value is: {element}");
}// no semicolon here

for number in 1..4 {// range [1, 4) = [1, 2, 3]
println!("{}!", number);
}

for number in (1..4).rev() {// reverse
println!("{}!", number);
}


}

fn another_function(x: i32) {// type annotation
println!("Another function.");
println!("The value of x is: {x}");
}

fn add_five(x: i32) -> i32 {
5 + x // no semicolon means return
}

Lecture 4 Understanding Ownership

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
fn main() {
//Ownership is a set of rules that govern how a Rust program manages memory.
//These rules are checked at compile time.
//Ownership is Rust's most unique feature, and it enables Rust to make memory safety guarantees without needing a garbage collector.
//All programs have to manage the way they use a computer's memory while running.

//Stack and Heap
//All data stored on the stack must have a known, fixed size.
//Data with an unknown size at compile time or a size that might change must be stored on the heap instead.
//The main purpose of ownership is to manage heap data.

//Ownership Rules
//1. Each value in Rust has a variable that’s called its owner.
//2. There can only be one owner at a time.
//3. When the owner goes out of scope, the value will be dropped.

//Variable Scope
let mut s = String::from("hello");//The String type is allocated on the heap.
s.push_str(", world!"); // push_str() appends a literal to a String
println!("{}", s); // This will print `hello, world!`

//Memory and Allocation

//With the String type, in order to support a mutable, growable piece of text, we need to allocate an amount of memory on the heap, unknown at compile time, to hold the contents. This means:
//1. The memory must be requested from the memory allocator at runtime.
//2. We need a way of returning this memory to the allocator when we’re done with our String.

//Rust takes a different path:
//the memory is automatically returned once the variable that owns it goes out of scope.

//Move
let x = 5;
let y = x; //Stack-Only Data: Copy
println!("x = {}, y = {}", x, y);//This works because integers are simple values with a known, fixed size, and these two 5 values are pushed onto the stack.

let s1 = String::from("hello");
let s2 = s1;
//println!("{}, world!", s1);//This will throw an error because Rust considers s1 to no longer be valid and, therefore, Rust doesn’t need to free anything when s1 goes out of scope.
println!("{}, world!", s2);//This will work because s2 is the new owner of the String data.

//Clone
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);//This will work because s1.clone() creates a deep copy of the String data, not just a copy of the stack pointer.

//Ownership and Functions
let s = String::from("hello"); // s comes into scope.
takes_ownership(s); // s's value moves into the function...
// ... and so is no longer valid here.
let x = 5; // x comes into scope.
makes_copy(x); // x would move into the function,
// but i32 is Copy, so it’s okay to still
// use x afterward.
//println!("{}", s);//This will throw an error because s is no longer valid.
println!("{}", x);//This will work because x is still valid.

//Return Values and Scope
let s1 = gives_ownership(); // gives_ownership moves its return
// value into s1.
let s2 = String::from("hello"); // s2 comes into scope.
let s3 = takes_and_gives_back(s2); // s2 is moved into
// takes_and_gives_back, which also
// moves its return value into s3.
println!("s1 = {}, s3 = {}", s1, s3);//Here, s3 goes out of scope and is dropped. s2 goes out of scope but was moved, so nothing happens. s1 goes out of scope and is dropped.
//println!("{}", s2);//This will throw an error because s2 is no longer valid.

//References and Borrowing
let s1 = String::from("hello");
let len = calculate_length(&s1);//The &s1 syntax lets us create a reference that refers to the value of s1 but does not own it.
println!("The length of '{}' is {}.", s1, len);//This will work because s1 is still valid.

//Mutable References
let mut s = String::from("hello");
change(&mut s);//We can have only one mutable reference to a particular piece of data in a particular scope.
println!("{}", s);//This will work because s is still valid.

let mut s = String::from("hello");

let r1 = &mut s;
//let r2 = &mut s;//This will throw an error because we can have only one mutable reference to a particular piece of data in a particular scope.

//println!("{}, {}", r1, r2);
println!("{}", r1);

let mut s = String::from("hello");

{
let r1 = &mut s;
println!("{}", r1);
} // r1 goes out of scope here, so we can make a new reference with no problems.

let r2 = &mut s;
println!("{}", r2);

let s = String::from("hello");

let r1 = &s; // no problem
let r2 = &s; // no problem
//let r3 = &mut s; // BIG PROBLEM //This will throw an error because we can have either one mutable reference or any number of immutable references.

println!("{}, {}", r1, r2);

//Note that a reference’s scope starts from where it is introduced and continues through the last time that reference is used.
let mut s = String::from("hello");

let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{} and {}", r1, r2);
// variables r1 and r2 will not be used after this point

let r3 = &mut s; // no problem
println!("{}", r3);

//Dangling References
let reference_to_nothing = dangle();
println!("{}", reference_to_nothing);

//The Rules of References
//1. At any given time, you can have either one mutable reference or any number of immutable references.
//2. References must always be valid.

//The Slice Type
//Slices let you reference a contiguous sequence of elements in a collection rather than the whole collection. A slice is a kind of reference, so it does not have ownership.

//Find the first word in a string
let s = String::from("hello world");
//let wordIndex = first_word(&s);
let first = first_word(&s);
//s.clear();//This will throw an error because we have an immutable reference to s.
println!("the first word is: {}", first);

let hello = &s[..5]; //[starting_index..ending_index) = [0..5) = [0, 1, 2, 3, 4]
let world = &s[6..]; //[starting_index..ending_index) = [6..11) = [6, 7, 8, 9, 10]
println!("{} {}", hello, world);

//Other Slices
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];//[starting_index..ending_index) = [1..3) = [1, 2]
assert_eq!(slice, &[2, 3]);//The assert_eq! macro tests whether two expressions are equal to each other; if they are, nothing happens, and if they aren’t, the macro prints the two expressions and panics.

}

fn takes_ownership(some_string: String) { // some_string comes into scope.
println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope.
println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.

fn gives_ownership() -> String { // gives_ownership will move its
// return value into the function
// that calls it.
let some_string = String::from("hello"); // some_string comes into scope.
some_string // some_string is returned and
// moves out to the calling
// function.
}

fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
// scope.
a_string // a_string is returned and moves out to the calling function.
}

fn calculate_length(s: &String) -> usize {//We call having references as function parameters borrowing.
s.len()
}//Here, s goes out of scope. But because it does not have ownership of what it refers to, nothing happens.

fn change(some_string: &mut String) {
some_string.push_str(", world");
}

// fn dangle() -> &String { //This will throw an error because we are trying to return a reference to a String that is created inside the function.
// let s = String::from("hello");
// &s
// }//Here, s goes out of scope, and is dropped. Its memory goes away. Danger!

fn dangle() -> String {
let s = String::from("hello");
s
}

// fn first_word(s: &String) -> usize {
// let bytes = s.as_bytes();

// for (i, &item) in bytes.iter().enumerate() {
// if item == b' ' {
// return i;
// }
// }

// s.len()
// }

fn first_word(s: &String) -> &str {//We can use &str as the type of the slice parameter to make it clear that the keys function will return slices of String values rather than whole String values.
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {//The enumerate method returns a tuple consisting of the index and the reference to the element.
if item == b' ' {
return &s[0..i];
}
}

&s[..]
}

//1. fn first_word(s: &String) -> &str
//2. fn first_word(s: &str) -> &str
//A more experienced Rustacean would write the signature shown in type 2 instead because it allows us to use the same function on both &String values and &str values.