Rust学习笔记

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

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

Lecture 13: Functional Language Features Iterators and Closures

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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
use std::thread;
use std::time::Duration;

//Fn Traits : Fn, FnMut, FnOnce

struct Cacher<T>
where T: Fn(u32) -> u32
{
calculation: T,
value: Option<u32>,
}

impl<T> Cacher<T>
where T:
Fn(u32) -> u32,
{
fn new(calculation: T) -> Cacher<T> {
Cacher {
calculation,
value: None,
}
}

fn value(&mut self, arg: u32) -> u32 {
match self.value {
Some(v) => v,
None => {
let v = (self.calculation)(arg);
self.value = Some(v);
v
}
}
}
}

#[derive(PartialEq, Debug)]
pub struct Shoe {
size: u32,
style: String,
}

pub fn shoes_in_my_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
shoes.into_iter()
.filter(|s| s.size == shoe_size)
.collect()
}

fn main() {
let simulated_user_specified_value = 10;
let simulated_random_number = 7;

generate_workout(
simulated_user_specified_value,
simulated_random_number
);

let x = 4;
let equal_to_x = |z| z == x; //closure can use variable from the scope in which they’re defined
// fn equal_to_x(z: i32) -> bool { z == x } //function can't use variable from the scope in which they’re defined
let y = 4;
assert!(equal_to_x(y));

//capturing the environment with closures
// 1. FnOnce: consumes the variables it captures from its enclosing scope, known as the closure’s environment.
// 2. FnMut: can change the environment because it mutably borrows values.
// 3. Fn: borrows values from the environment immutably.
// Fn > FnMut > FnOnce

// move the ownership of the environment variables into the closure
let x = vec![1, 2, 3];
let equal_to_x = move |z| z == x; //x is moved into the closure and so the closure owns x
// println!("can't use x here: {:?}", x); //error: value borrowed here after move
let y = vec![1, 2, 3];
assert!(equal_to_x(y));

//iterators
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter(); //v1_iter is immutable
for val in v1_iter {
println!("Got: {}", val);
}

//iterator trait
// pub trait Iterator {
// type Item;
// fn next(&mut self) -> Option<Self::Item>;
// }

}

fn _simulated_expensive_calculation(intensity: u32) -> u32 {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
intensity
}

//compare function and closure
// 1. function: fn add_one_v1(x: u32) -> u32 { x + 1 }
// 2. closure: let add_one_v2 = |x: u32| -> u32 { x + 1 };
// 3. closure: let add_one_v3 = |x| { x + 1 };
// 4. closure: let add_one_v4 = |x| x + 1;

// The compiler will infer only one concrete type for each parameter and for the return value
// of closures, because the compiler will analyze the body of the closure to determine the types
// of each parameter and the return type. If there are multiple ways to infer the types, the
// compiler will error. This code will not compile because the compiler can’t determine the
// types of the closure’s parameters or the return type of the closure:

// let example_closure = |x| x;
// let s = example_closure(String::from("hello"));
// let n = example_closure(5);

fn generate_workout(intensity: u32, random_num: u32) {

// let expensive_closure = |num: u32| -> u32{ //the compiler can infer the type of the parameter and the return type
// println!("calculating slowly...");
// thread::sleep(Duration::from_secs(2));
// num
// };

let mut expensive_closure = Cacher::new(|num| {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
});

// let expensive_result = simulated_expensive_calculation(intensity);
if intensity < 25 {
// println!(
// "Today, do {} pushups!",
// simulated_expensive_calculation(intensity)
// );
// println!(
// "Next, do {} situps!",
// simulated_expensive_calculation(intensity)
// );

//optimization 1
// println!(
// "Today, do {} pushups!",
// expensive_result
// );
// println!(
// "Next, do {} situps!",
// expensive_result
// );
println!(
"Today, do {} pushups!",
expensive_closure.value(intensity)
);
println!(
"Next, do {} situps!",
expensive_closure.value(intensity)
);
} else {
if random_num == 3 {
println!("Take a break today! Remember to stay hydrated!");
} else {
// println!(
// "Today, run for {} minutes!",
// simulated_expensive_calculation(intensity)
// );

// println!(
// "Today, run for {} minutes!",
// expensive_result
// );

println!(
"Today, run for {} minutes!",
expensive_closure.value(intensity)
);
}
}
}

//custom iterator
struct Counter {
count: u32,
}

impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}

}

impl Iterator for Counter {
type Item = u32; //associated type

fn next(&mut self) -> Option<Self::Item> {
self.count += 1;
if self.count < 6{
Some(self.count)
} else {
None
}
}
}

//Zero Cost Abstractions: the abstraction doesn’t incur any additional runtime overhead
//because the compiler generates code as if we had written the lower-level or more repetitive code by hand.

#[cfg(test)]
mod tests {
#[test]
fn call_with_different_values(){
let mut c = super::Cacher::new(|a| a);

let v1 = c.value(1);
assert_eq!(v1, 1);
let v2 = c.value(2);
assert_ne!(v2, 2);
}

#[test]
fn iterator_demonstration(){
let v1 = vec![1, 2, 3];
let mut v1_iter = v1.iter(); //v1_iter is mutable
assert_eq!(v1_iter.next(), Some(&1)); //next is a consuming adaptor because it takes ownership of the iterator
assert_eq!(v1_iter.next(), Some(&2));
assert_eq!(v1_iter.next(), Some(&3));
assert_eq!(v1_iter.next(), None); //next is a consuming adaptor because it takes ownership of the iterator
}

#[test]
fn iterator_sum(){
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
let total: i32 = v1_iter.sum(); //sum is a consuming adaptor because it takes ownership of the iterator
assert_eq!(total, 6);
}

#[test]
fn iterator_map(){
let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); //map is a consuming adaptor because it takes ownership of the iterator
assert_eq!(v2, vec![2, 3, 4]);
}


#[test]
fn filter_by_size(){
let shoes = vec![
super::Shoe { size: 10, style: String::from("sneaker") },
super::Shoe { size: 13, style: String::from("sandal") },
super::Shoe { size: 10, style: String::from("boot") },
];
let in_my_size = super::shoes_in_my_size(shoes, 10);

assert_eq!(
in_my_size,
vec![
super::Shoe { size: 10, style: String::from("sneaker") },
super::Shoe { size: 10, style: String::from("boot") },
]
);
}

#[test]
fn calling_next_directly(){
let mut counter = super::Counter::new();
assert_eq!(counter.next(), Some(1));
assert_eq!(counter.next(), Some(2));
assert_eq!(counter.next(), Some(3));
assert_eq!(counter.next(), Some(4));
assert_eq!(counter.next(), Some(5));
assert_eq!(counter.next(), None); //next is a consuming adaptor because it takes ownership of the iterator
}

#[test]
fn using_other_iterator_trait_methods(){
let sum: u32 = super::Counter::new()
.zip(super::Counter::new().skip(1))
.map(|(a, b)| a * b)
.filter(|x| x % 3 == 0)
.sum(); //sum is a consuming adaptor because it takes ownership of the iterator
assert_eq!(18, sum);
}
}

Improving Our I/O Project: mini_grep

main.rs

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
//grep: globally search a regular expression and print

use std::env;//command line arguments
use std::process;//exit

use minigrep::Config;//Config struct
use minigrep::run;//run function


//Separation of Concerns for Binary Projects
//Splitting code into a main.rs and a lib.rs is a good default choice when starting a binary project.

//1. Split your program into a main.rs and a lib.rs and move your program’s logic to lib.rs.
//2. As long as your command line parsing logic is small, it can remain in main.rs.
//3. When the command line parsing logic starts getting complicated, extract it from main.rs and move it to lib.rs.

fn main() {
// let args: Vec<String> = env::args().collect();//collect command line arguments

let args = env::args();//iterator over the command line arguments

// println!("{:?}", args);//print command line arguments //[./target/debug/minigrep, xxxx, yyyy]

// let query = &args[1];//query string
// let filename = &args[2];//filename

//let (query, filename) = parse_config(&args[1..]);//parse command line arguments

// let config = parse_config(&args);//parse command line arguments

// let config = Config::new(&args);//parse command line arguments

let config = Config::build(args).unwrap_or_else(|err| {
// println!("Problem parsing arguments: {}", err);
eprintln!("Problem parsing arguments: {}", err);//error handling: print to stderr
process::exit(1);
});//parse command line arguments

// println!("Searching for {}", query);
// println!("In file {}", filename);

// let contents = fs::read_to_string(config.filename)
// .expect("Something went wrong reading the file");//read file

// println!("With text:\n{}", contents);

if let Err(e) = run(config){
// println!("Application error: {}", e);
eprintln!("Application error: {}", e);//error handling: print to stderr
process::exit(1);
}
}

lib.rs

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
use std::fs;//file system
use std::error::Error;//error handling
use std::env;//environment variables

// fn parse_config(args: &[String]) -> (&str, &str) {
// let query = &args[1];//query string
// let filename = &args[2];//filename

// (query, filename)
// }

pub fn run(config: Config) -> Result<(), Box<dyn Error>>{
let contents = fs::read_to_string(config.filename)?;
//.expect("Something went wrong reading the file");//read file
//println!("With text:\n{}", contents);
let results = if config.case_sensitive {//if case sensitive
search(&config.query, &contents)//search case sensitive
} else {
search_case_insensitive(&config.query, &contents)//search case insensitive
};
// for line in search(&config.query, &contents) {//iterate over each line
// println!("{}", line);//print line
// }
for line in results {//iterate over each line
println!("{}", line);//print line
}
Ok(())
}

pub struct Config {
query: String,
filename: String,
case_sensitive: bool,
}


// fn parse_config(args: &[String]) -> Config {
// let query = args[1].clone();//query string
// let filename = args[2].clone();//filename

// Config { query, filename }
// }

impl Config {
// fn new(args: &[String]) -> Config {
// if args.len() < 3 {
// panic!("not enough arguments");
// }
// let query = args[1].clone();//query string
// let filename = args[2].clone();//filename

// Config { query, filename }
// }

//pub fn build(args: &[String]) -> Result<Config, &'static str> {
pub fn build(mut args: std::env::Args) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}

// let query = args[1].clone();
// let filename = args[2].clone();
args.next();//skip program name
let query = match args.next() {//query string
Some(arg) => arg,
None => return Err("Didn't get a query string"),
};

let filename = match args.next() {//filename
Some(arg) => arg,
None => return Err("Didn't get a file name"),
};


let case_sensitive = env::var("CASE_INSENSITIVE").is_err();//case sensitive
Ok(Config {
query,
filename,
case_sensitive
})
}
}

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {//<'a> lifetime annotation
// let mut results = Vec::new();//mutable vector

// for line in contents.lines() {//iterate over each line
// if line.contains(query) {//if line contains query
// results.push(line);//add line to results
// }
// }
// results//return results

contents.lines()//iterate over each line
.filter(|line| line.contains(query))//if line contains query
.collect()//collect into vector
}

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {//<'a> lifetime annotation
//let mut results = Vec::new();//mutable vector
let query = query.to_lowercase();//convert query to lowercase
// for line in contents.lines() {//iterate over each line
// if line.to_lowercase().contains(&query) {//if line contains query
// results.push(line);//add line to results
// }
// }
// results//return results
contents.lines()//iterate over each line
.filter(|line| line.to_lowercase().contains(&query))//if line contains query
.collect()//collect into vector
}

//TDD: Test-Driven Development
//Writing a Failing Test and Seeing It Pass
//1. Write a test that fails and run it to make sure it fails for the reason you expect.
//2. Write or modify just enough code to make the new test pass.
//3. Refactor the code you just added or changed and make sure the tests continue to pass.
//4. Repeat from step 1!

#[cfg(test)]
mod tests {
use super::*;//import outer scope

#[test]
fn one_result() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.";
assert_eq!(
vec!["safe, fast, productive."],
search(query, contents)
);
}

#[test]
fn case_sensitive() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
assert_eq!(
vec!["safe, fast, productive."],
search(query, contents)
);
}

#[test]
fn case_insensitive() {
let query = "rUsT";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";
assert_eq!(
vec!["Rust:", "Trust me."],
search_case_insensitive(query, contents)
);
}
}