Rust7.1 Functional Language Features Iterators and Closures

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

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

//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

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)
        );
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值