X-Git-Url: https://git.ralfj.de/rust-101.git/blobdiff_plain/e73ddf5e1d4768cb86fba3eb583f4fec0286acff..30fb994332d6d62ffd199e6e7b45b4a5c84abf83:/workspace/src/part10.rs?ds=inline diff --git a/workspace/src/part10.rs b/workspace/src/part10.rs index ee226c4..8fc650f 100644 --- a/workspace/src/part10.rs +++ b/workspace/src/part10.rs @@ -1,19 +1,20 @@ -// Rust-101, Part 09: Closures (WIP) -// ================================= - -use std::io::prelude::*; -use std::io; +// Rust-101, Part 10: Closures +// =========================== +use std::fmt; use part05::BigInt; + +// So, let us define a trait that demands that the type provides some method `do_action` on digits. trait Action { fn do_action(&mut self, digit: u64); } +// Now we can write a function that takes some `a` of a type `A` such that we can call `do_action` on `a`, passing it every digit. impl BigInt { fn act_v1(&self, mut a: A) { for digit in self { - a.do_action(digit); + unimplemented!() } } } @@ -23,37 +24,76 @@ struct PrintWithString { } impl Action for PrintWithString { + // Here we perform the actual printing of the prefix and the digit. We're not making use of our ability to + // change `self` here, but we could replace the prefix if we wanted. fn do_action(&mut self, digit: u64) { - println!("{}{}", self.prefix, digit); + unimplemented!() } } -fn read_one_line() -> String { - println!("Please enter a line of text."); - let mut stdin = io::stdin(); - let mut prefix = "".to_string(); - stdin.read_line(&mut prefix).unwrap(); - prefix +// Finally, this function takes a `BigInt` and a prefix, and prints the digits with the given prefix. +fn print_with_prefix_v1(b: &BigInt, prefix: String) { + let my_action = PrintWithString { prefix: prefix }; + b.act_v1(my_action); } -pub fn main_v1() { - let prefix = read_one_line(); - let my_action = PrintWithString { prefix: prefix }; +// Here's a small main function, demonstrating the code above in action. Remember to edit `main.rs` to run it. +pub fn main() { let bignum = BigInt::new(1 << 63) + BigInt::new(1 << 16) + BigInt::new(1 << 63); - bignum.act_v1(my_action); + print_with_prefix_v1(&bignum, "Digit: ".to_string()); } +// ## Closures + +// This defines `act` very similar to above, but now we demand `A` to be the type of a closure that mutates its borrowed environment, +// takes a digit, and returns nothing. impl BigInt { fn act(&self, mut a: A) { for digit in self { - a(digit); + // We can call closures as if they were functions - but really, what's happening here is translated to essentially what we wrote above, in `act_v1`. + unimplemented!() } } } -pub fn main() { - let prefix = read_one_line(); - let bignum = BigInt::new(1 << 63) + BigInt::new(1 << 16) + BigInt::new(1 << 63); - bignum.act(|digit| println!("{}{}", prefix, digit) ); +// Now that we saw how to write a function that operates on closures, let's see how to write a closure. +pub fn print_with_prefix(b: &BigInt, prefix: String) { + b.act(|digit| println!("{}{}", prefix, digit) ); +} +// You can change `main` to call this function, and you should notice - nothing, no difference in behavior. +// But we wrote much less boilerplate code! + +// Remember that we decided to use the `FnMut` trait above? This means our closure could actually mutate its environment. +// For example, we can use that to count the digits as they are printed. +pub fn print_and_count(b: &BigInt) { + let mut count: usize = 0; + b.act(|digit| { println!("{}: {}", count, digit); count = count +1; } ); + println!("There are {} digits", count); } +// ## Fun with iterators and closures + +// Let's say we want to write a function that increments every entry of a `Vec` by some number, then looks for numbers larger than some threshold, and prints them. +fn inc_print_even(v: &Vec, offset: i32, threshold: i32) { + for i in v.iter().map(|n| *n + offset).filter(|n| *n > threshold) { + println!("{}", i); + } +} + +// Sometimes it is useful to know both the position of some element in a list, and its value. That's where the `enumerate` function helps. +fn print_enumerated(v: &Vec) { + for (i, t) in v.iter().enumerate() { + println!("Position {}: {}", i, t); + } +} + +// And as a final example, one can also collect all elements of an iterator, and put them, e.g., in a vector. +fn filter_vec_by_divisor(v: &Vec, divisor: i32) -> Vec { + unimplemented!() +} + +// **Exercise 10.1**: Look up the [documentation of `Iterator`](https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html) to learn about more functions +// that can act on iterators. Try using some of them. What about a function that sums the even numbers of an iterator? Or a function that computes the +// product of those numbers that sit at odd positions? A function that checks whether a vector contains a certain number? Whether all numbers are +// smaller than some threshold? Be creative! +