Today, I'm free to take a look at chapter 4 - functions, closures and iterators
Chapter 4: functions, closures and iteratorsRust is a language that supports functional programming. Function, as a first-class citizen, is itself a type. Function types can be used as parameters or return values of other functions, can also be assigned to other variables, and can also be called and executed directly.
function
Function name: snakecase is separated by specification. All letters are lowercase and words are separated by underscores
fn add(x: i32, y: i32) -> i32 { x + y } fn main() { let x = 5; let y = { let x = 2; x + 1 }; let sum = add(x , y); println!("{} + {} = {}", x, y, sum) }
notes:
- The parameter of a function must specify the parameter type, and the default value of the parameter cannot be specified
- If the statement without semicolon is an expression, the value of the expression will be returned automatically, but the statement does not return a value
- Parameters are divided into variable and immutable parameters, which are immutable by default. When variable operations are required, use the mut keyword.
- Each function of Rust has a return value. Even functions that do not display the return value will implicitly return a cell system ()
Methods and functions
Well, I know everything
#[derive(Debug, PartialEq)] pub struct Student { name: &'static str, score: i32, } impl Student { // Inside the structure, but not with & self as the first parameter, is called a correlation function // The following is equivalent to a constructor pub fn new(name: &'static str, score: i32) -> Self { Student { name, score } } // Difference between method and function: the first parameter must be & self, indicating the reference of this instance object pub fn get_name(&self) -> &str { self.name } pub fn set_name(&mut self, name: &'static str) { self.name = name } pub fn get_score(&self) -> i32 { self.score } pub fn set_score(&mut self, score: i32) { self.score = score } } fn main() { let mut student: Student = Student::new("zhangsan", 59); println!("name : {},score : {}", student.get_name(), student.get_score()); student.set_score(60); println!("{:?}",student) }
Higher order function
High order function refers to the function with function as parameter or return value. It is the most basic feature of functional programming language.
Then there will be function pointers and functions as parameters
Function pointerfn main() { // Here, only the function name is used, not the calling function // The specified type must be displayed let fn_ptr: fn() = hello; //Print out the pointer address and prove that it is a function pointer // Here my idea will display an error, but there is no error when running!!! println!("{:p}", fn_ptr); fn_ptr(); }Function as parameter
You can use type to define aliases for function pointer types
type MathOp = fn(i32, i32) -> i32; fn math(op: MathOp, x: i32, y: i32) -> i32 { println!("{:p}", op); op(x, y) } fn subtract(x: i32, y: i32) -> i32 { x - y } fn main() { let (x, y) = (8, 3); println!("add operation result: {}", math(add, x, y)); println!("subtraction operation result: {}", math(subtract, x, y)); }Function as return value
Function as the return value, you can also use the type keyword to define an alias for the function pointer type
fn math_op(op: &str) -> matpOP { match op { "add" => add, _ => subtract, } } fn main() { let (x, y) = (8, 3); let mut op = math_op("add"); println!("{}", op(x, y)) }
closure
It refers to creating an anonymous function in a function. Although there is no function name, the closure can be assigned to a variable.
Basic grammarA closure is a combination of a pipe | | and braces {},
The parameter type of closure can be ignored,
The return value type can be specified after the pipe character, but it is not required.
If the closure body has only one line, you can ignore braces
fn main() { let add_one = |x: i32| -> i32 ; println!("{}", add_one(1)); }Type inference
The compiler can reliably infer the types of parameters and return values.
fn main() { fn add_one_v1(x: u32) -> u32 { x + 1 } let add_one_v2 = |x: u32| -> u32{ x + 1 }; let add_one_v3 = |x| -> u32{ x + 1 }; // Although the compiler can infer parameters and return values. However, if the same closure is called multiple times but different types are passed, an error will be reported let add_one_v4 = |x| x + 1; } // The above compilation error and the following are correct. The compiler must let it specify the parameter type fn main7() { fn add_one_v1(x: u32) -> u32 { x + 1 } let add_one_v2 = |x: u32| -> u32{ x + 1 }; let add_one_v3 = |x: u32| { x + 1 }; // Although the compiler can infer parameters and return values. However, if the same closure is called multiple times but different types are passed, an error will be reported let add_one_v4 = |x:u32| x + 1; }Capture variable
The biggest difference between closures and functions is that closures can capture and use variables in the scope in which they are defined
fn main (){ let i = 1; let add = |x| { x+ i }; println!("{}",add(7)) }
iterator
The iterator pattern abstracts the behavior of traversing the data set into a separate iterative object, so that when traversing the set, all elements in the set can be passed to the processing logic in order. Greatly simplify data operations
Iterator traitIt is an abstract interface of iterator pattern. There are two important interfaces:
- The iter method is used to return an iterator instance
- The next method returns the next element of the iterator. And encapsulate it in Some function. If you iterate to the end of the collection (after the last element), return None.
fn main() { let v = [1, 2, 3]; let mut iter = v.iter(); println!("{:?}", iter.next()); println!("{:?}", iter.next()); println!("{:?}", iter.next()); println!("{:?}", iter.next()); }Consumer
Rust's iterators are lazy and do not automatically traverse
You can do this by consuming the elements in the iterator.
sum, any, collect, etc. std:iter::Iterator contains
sumThe consumer sum can perform a sum operation on the elements of the iterator.
fn main() { let v = [1, 2, 3]; let total: i32 = v.iter().sum(); println!("total :{}", totall) }any
The consumer any can find out whether there are elements in the iterator that meet the condition.
fn main() { let v = [1, 3, 4, 5]; let result1 = v.iter().any(|&x| x == 2); let result2 = v.iter().any(|x| *x == 2); // ^^ no implementation for `& == ` // let result3 = v.iter().any(|x| x == 2); }
You can only use reference and dereference,
collectThe iterator can be converted to the specified container type, that is, the elements in the iterator can be collected into the specified container.
fn main() { let v1 = [1, 2, 3, 4, 5]; // Traverse into a map, and then into the Vec array container let v2: Vec<i32> = v1.iter().map(|x| x + 1).collect(); println!(":{:?}", v2) }iterator adaptor
You can convert the current iterator to another type of iterator and support chained calls to multiple iterator adapters.
However, all iterators are lazy, and there must be a consumer to get the call result of the iterator.
map, take, filter, rev and zip. Other iterators are found in std:: iter
mapCall the closure on each element in the iterator and generate a new iterator
fn main() { let v = [1, 2, 3]; let result: Vec<i32> = v.iter() .map(|x| x + 3) .collect(); println!("{:?}", result) }take
The adapter take generates a new iterator that iterates only the first n elements of the original iterator, which is often used to traverse the scene of the specified data elements
fn main() { let v = [1, 2, 3, 4, 5]; let result = v.iter().take(3); for i in result { println!("{}", i) } }filter
The adapter filter calls a closure on each element in the iterator and generates a new iterator that filters the elements. Closure will return true or false
fn main() { let v = [1, 2, 3, 4, 5]; let result = v.iter() .map(|x| x + 3) .filter(|x| x % 3 == 0) .collect(); println!("{:?}",result) }rev
rev can reverse the iteration direction of the iterator
fn main() { let v = [1, 2, 3, 4, 5]; let result = v.iter().rev(); for i in result { println!("{}", i) } }zip
You can compress two iterators together to produce a new iterator.
It iterates over two iterators simultaneously and returns a primitive ancestor, where the first element comes from the first iterator and the second element comes from the second iterator.
If any iterator returns None, the adapter zip returns None
fn main() { let v1 = [1, 2, 3]; let v2 = [1, 2, 3, 4, 5]; let result: Vec<i32> = v1.iter().zip(v2.iter()) .map(|(a, b)| a + b) .filter(|x| x % 3 == 0) .collect(); println!("{:?}",result) }