diff --git a/README.md b/README.md index b2458437..71818a3d 100644 --- a/README.md +++ b/README.md @@ -83,17 +83,18 @@ For your convenience, here is a list of all the exercises, with links to view th - [Exercise A - Variables & Scope](https://github.com/CleanCut/ultimate_rust_crash_course/tree/master/exercise/a_variables) - [Exercise B - Functions](https://github.com/CleanCut/ultimate_rust_crash_course/tree/master/exercise/b_functions) -- [Exercise C - Simple Types](https://github.com/CleanCut/ultimate_rust_crash_course/tree/master/exercise/c_simple_types) -- [Exercise D - Control Flow & Strings](https://github.com/CleanCut/ultimate_rust_crash_course/tree/master/exercise/d_control_flow_strings) -- [Exercise E - Ownership & References](https://github.com/CleanCut/ultimate_rust_crash_course/tree/master/exercise/e_ownership_references) -- [Exercise F - Structs & Traits](https://github.com/CleanCut/ultimate_rust_crash_course/tree/master/exercise/f_structs_traits) -- [Exercise G - Collections & Enums](https://github.com/CleanCut/ultimate_rust_crash_course/tree/master/exercise/g_collections_enums) -- [Exercise H - Closures & Threads](https://github.com/CleanCut/ultimate_rust_crash_course/tree/master/exercise/h_closures_threads) -- [Exercise Z - Final Project](https://github.com/CleanCut/ultimate_rust_crash_course/tree/master/exercise/z_final_project) +- [Exercise C - Modules](https://github.com/CleanCut/ultimate_rust_crash_course/tree/master/exercise/c_modules) +- [Exercise D - Simple Types](https://github.com/CleanCut/ultimate_rust_crash_course/tree/master/exercise/d_simple_types) +- [Exercise E - Control Flow](https://github.com/CleanCut/ultimate_rust_crash_course/tree/master/exercise/e_control_flow) +- [Exercise F - Strings](https://github.com/CleanCut/ultimate_rust_crash_course/tree/master/exercise/f_strings) +- [Exercise G - Ownership & References](https://github.com/CleanCut/ultimate_rust_crash_course/tree/master/exercise/g_ownership_references) +- [Exercise ? - Structs & Traits](https://github.com/CleanCut/ultimate_rust_crash_course/tree/master/exercise/) +- [Exercise ? - Collections & Enums](https://github.com/CleanCut/ultimate_rust_crash_course/tree/master/exercise/) +- [Exercise Z - Image Manipulation](https://github.com/CleanCut/ultimate_rust_crash_course/tree/master/exercise/z_final_project) # Projects -- [Invaders](https://github.com/CleanCut/invaders) - A terminal-based Space Invaders arcade game clone. +- TBD [exercises]: https://github.com/CleanCut/ultimate_rust_crash_course#exercises diff --git a/exercise/b_functions/src/main.rs b/exercise/b_functions/src/main.rs index fbfe73fb..dfb722ac 100644 --- a/exercise/b_functions/src/main.rs +++ b/exercise/b_functions/src/main.rs @@ -2,39 +2,63 @@ #![allow(unused_variables)] fn main() { - let width = 4; - let height = 7; - let depth = 10; - // 1. Try running this code with `cargo run` and take a look at the error. + let number: f64 = 3.989; // don't change this line! + + // 1. Try running the code and looking at the error. We would like to use the variable name + // `number` as an i32 (an integer), but it is already used as an f64 (a floating point number). // - // See if you can fix the error. It is right around here, somewhere. If you succeed, then - // doing `cargo run` should succeed and print something out. - { - let area = area_of(width, height); - } - println!("Area is {}", area); - - // 2. The area that was calculated is not correct! Go fix the area_of() function below, then run - // the code again and make sure it worked (you should get an area of 28). - - // 3. Uncomment the line below. It doesn't work yet because the `volume` function doesn't exist. - // Create the `volume` function! It should: - // - Take three arguments of type i32 - // - Multiply the three arguments together - // - Return the result (which should be 280 when you run the program). + // - Uncomment the commented-out code below + // - Complete the code to shadow the old `number` variable with a new `number` variable + // of the correct type. + + // ... = convert_to_integer(number); // uncomment this line and finish shadowing `number` + inspect_integer(number); // don't change this line! + + // 2. Uncomment and run the code below. Fix the scope problem so that the code compiles and runs + // producing the answer 42. + + // { + // let answer = 42; + // } + // println!("The answer is {}", answer); + + // 3. Create a function named `add` that adds two i32 values together and returns the result. + // Then uncomment the code below. You should get the output "4 + 42 = 46" // - // If you get stuck, remember that this is *very* similar to what `area_of` does. + // Note: If you fixed the scope problem from #2 by moving the `println` up into the nested + // scope, then you will have to change the code above again so that `answer` is in this scope. + + // let sum = ... // call your `add` function and pass it `number` and `answer` as arguments. + // println!("{} + {} = {}", number, answer, sum); + + // 4. You can declare a variable without initializing it, but the compiler must be able to + // ensure that it will always be initialized before you can use it. // - //println!("Volume is {}", volume(width, height, depth)); + // Uncomment and run the code below to see the error. Fix the error by setting countdown to 0 + // in the `else` branch of the `if` expression. Run the code. You should see a countdown of 10. + + // let countdown: i32; // declares countdown, but doesn't initialize it + // if answer < 100 { + // countdown = 10; + // } else { + // println!("The answer is clearly wrong."); + // // set countdown to some value here + // } + // println!("The countdown begins at {}", countdown); } -fn area_of(x: i32, y: i32) -> i32 { - // 2a. Fix this function to correctly compute the area of a rectangle given - // dimensions x and y by multiplying x and y and returning the result. - // - return 0; - // Challenge: It isn't idiomatic (the normal way a Rust programmer would do things) to use - // `return` on the last line of a function. Change the last line to be a - // "tail expression" that returns a value without using `return`. - // Hint: `cargo clippy` will warn you about this exact thing. +fn inspect_integer(x: i32) { + println!("The integer is {}", x); +} + +// Challenge: A "tail expression" is when the last expression in a block does not end with a +// semicolon, making it the value of the block. +// +// - Refactor the body of this function to be a "tail expression" instead of a return statement. +// - Make the same change to the `add` function that you created +// - Run the code and make sure you get the same output as you did before +fn convert_to_integer(num: f64) -> i32 { + // For more information on using `as` to cast between numeric types, see: + // https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast + return num.round() as i32; } diff --git a/exercise/h_closures_threads/Cargo.toml b/exercise/c_modules/Cargo.toml similarity index 76% rename from exercise/h_closures_threads/Cargo.toml rename to exercise/c_modules/Cargo.toml index 6d80d905..bf553e32 100644 --- a/exercise/h_closures_threads/Cargo.toml +++ b/exercise/c_modules/Cargo.toml @@ -1,9 +1,8 @@ [package] -name = "h_closures_threads" +name = "animal" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -crossbeam = "0.8.2" diff --git a/exercise/c_simple_types/README.md b/exercise/c_modules/README.md similarity index 100% rename from exercise/c_simple_types/README.md rename to exercise/c_modules/README.md diff --git a/exercise/c_modules/src/main.rs b/exercise/c_modules/src/main.rs new file mode 100644 index 00000000..f629e03b --- /dev/null +++ b/exercise/c_modules/src/main.rs @@ -0,0 +1,67 @@ +// 1. Organize code into a library without changing the output of the program. +// +// For each step of this exercise, you should be able to run the program before and after your +// change without affecting the output of the program. +// +// Move the constants below (FIRST, SECOND, and THIRD) into the library: +// - Create a `src/lib.rs` file +// - Move all of the constants into lib.rs +// - Make the constants public by adding the `pub` keyword in front of them +// - Add `use` statement(s) to main.rs to bring the constants into scope. +// +// Hint: the name of the library is defined in Cargo.toml + +const FIRST: i32 = 1; +const SECOND: i32 = 2; +const THIRD: i32 = 3; + +// 2. Create a library module named `sound` and move the animal functions into it. +// +// - In your lib.rs file add the line `pub mod sound;` +// - Create a `src/sound.rs` file for your module +// - Move the `dog`, `cat`, and `fox` functions into sound.rs +// - Make the functions public by adding the `pub` keyword in front of them +// - Add a `use` statement to bring the `sound` module into scope. +// - Change the function calls to access the functions through the `sound` module. +// For example: sound::dog() + +fn dog() { + println!("Dog goes WOOF!"); +} + +fn cat() { + println!("Cat goes MEOW!"); +} + +fn fox() { + println!("What does the fox say???"); +} + +fn main() { + print!("Listening to animal {}: ", FIRST); + dog(); + + print!("Listening to animal {}: ", SECOND); + cat(); + + print!("Listening to animal {}: ", THIRD); + fox(); +} + +// Challenge 1 +// +// - Move the `dog` and `cat` functions into a submodule `animal::sound::tame` +// - Move the `fox` function into a submodule `animal::sound::wild` +// +// Hint: You will need to create a subdirectory for the top-level `sound` modules' submodules to +// be placed in. + +// Challenge 2 +// +// Create an `animal::prelude` module which re-exports all of the constants and functions of the +// library. (A real library would only re-export the most commonly-used items in its prelude.) +// +// Change your `use` statement(s) in main.rs to just `use animal::prelude::*` +// +// Hint: You will need `pub use` to re-export an item, for more details see: +// https://doc.rust-lang.org/reference/items/use-declarations.html#use-visibility diff --git a/exercise/c_simple_types/src/main.rs b/exercise/c_simple_types/src/main.rs deleted file mode 100644 index 0fdcb291..00000000 --- a/exercise/c_simple_types/src/main.rs +++ /dev/null @@ -1,83 +0,0 @@ -// Silence some warnings so they don't distract from the exercise. -#![allow(dead_code, unused_variables)] - -fn main() { - let coords: (f32, f32) = (6.3, 15.0); - // 1. Pass parts of `coords` to the `print_difference` function. This should show the difference - // between the two numbers in coords when you do `cargo run`. Use tuple indexing. - // - // The `print_difference` function is defined below the `main` function. It may help if you look - // at how it is defined. - // - //print_difference( ... ); // Uncomment and finish this line - - - // 2. We want to use the `print_array` function to print coords...but coords isn't an array! - // Create an array of type [f32; 2] and initialize it to contain the - // information from coords. Uncomment the print_array line and run the code. - // - //let coords_arr... // create an array literal out of parts of `coord` here - //print_array(coords_arr); // and pass it in here (this line doesn't need to change) - - - let series = [1, 1, 2, 3, 5, 8, 13]; - // 3. Make the `ding` function happy by passing it the value 13 out of the `series` array. - // Use array indexing. Done correctly, `cargo run` will produce the additional output - // "Ding, you found 13!" - // - //ding(...); - - - let mess = ([3, 2], 3.14, [(false, -3), (true, -100)], 5, "candy"); - // 4. Pass the `on_off` function the value `true` from the variable `mess`. Done correctly, - // `cargo run` will produce the additional output "Lights are on!" I'll get you started: - // - //on_off(mess.2 ...); - - // 5. What a mess -- functions in a binary! Let's get organized! - // - // - Make a library file (src/lib.rs) - // - Move all the functions (except main) into the library - // - Make all the functions public with `pub` - // - Bring all the functions into scope using use statements. Remember, the name of the library - // is defined in Cargo.toml. You'll need to know that to `use` it. - // - // `cargo run` should produce the same output, only now the code is more organized. 🎉 - - // Challenge: Uncomment the line below, run the code, and examine the - // output. Then go refactor the print_distance() function according to the - // instructions in the comments inside that function. - - // print_distance(coords); -} - -fn print_difference(x: f32, y: f32) { - println!("Difference between {} and {} is {}", x, y, (x - y).abs()); -} - -fn print_array(a: [f32; 2]) { - println!("The coordinates are ({}, {})", a[0], a[1]); -} - -fn ding(x: i32) { - if x == 13 { - println!("Ding, you found 13!"); - } -} - -fn on_off(val: bool) { - if val { - println!("Lights are on!"); - } -} - -fn print_distance(z: (f32, f32)) { - // Using z.0 and z.1 is not nearly as nice as using x and y. Lucky for - // us, Rust supports destructuring function arguments. Try replacing "z" in - // the parameter list above with "(x, y)" and then adjust the function - // body to use x and y. - println!( - "Distance to the origin is {}", - ( z.0.powf(2.0) + z.1.powf(2.0) ).sqrt()); -} - diff --git a/exercise/d_control_flow_strings/src/main.rs b/exercise/d_control_flow_strings/src/main.rs deleted file mode 100644 index 416c4c4f..00000000 --- a/exercise/d_control_flow_strings/src/main.rs +++ /dev/null @@ -1,58 +0,0 @@ -// Silence some warnings so they don't distract from the exercise. -#![allow(dead_code, unused_mut, unused_variables)] - -fn main() { - // This collects any command-line arguments into a vector of Strings. - // For example: - // - // cargo run apple banana - // - // ...produces the equivalent of - // - // vec!["apple".to_string(), "banana".to_string()] - let args: Vec = std::env::args().skip(1).collect(); - - // This consumes the `args` vector to iterate through each String - for arg in args { - // 1a. Your task: handle the command-line arguments! - // - // - If arg is "sum", then call the sum() function - // - If arg is "double", then call the double() function - // - If arg is anything else, then call the count() function, passing "arg" to it. - - - // 1b. Now try passing "sum", "double" and "bananas" to the program by adding your argument - // after "cargo run". For example "cargo run sum" - } -} - -fn sum() { - let mut sum = 0; - // 2. Use a "for loop" to iterate through integers from 7 to 23 *inclusive* using a range - // and add them all together (increment the `sum` variable). Hint: You should get 255 - // Run it with `cargo run sum` - - - println!("The sum is {}", sum); -} - -fn double() { - let mut count = 0; - let mut x = 1; - // 3. Use a "while loop" to count how many times you can double the value of `x` (multiply `x` - // by 2) until `x` is larger than 500. Increment `count` each time through the loop. Run it - // with `cargo run double` Hint: The answer is 9 times. - - - println!("You can double x {} times until x is larger than 500", count); -} - -fn count(arg: String) { - // Challenge: Use an unconditional loop (`loop`) to print `arg` 8 times, and then break. - // You will need to count your loops, somehow. Run it with `cargo run bananas` - // - // print!("{} ", arg); // Execute this line 8 times, and then break. `print!` doesn't add a newline. - - - println!(); // This will output just a newline at the end for cleanliness. -} diff --git a/exercise/c_simple_types/Cargo.toml b/exercise/d_simple_types/Cargo.toml similarity index 100% rename from exercise/c_simple_types/Cargo.toml rename to exercise/d_simple_types/Cargo.toml diff --git a/exercise/d_control_flow_strings/README.md b/exercise/d_simple_types/README.md similarity index 100% rename from exercise/d_control_flow_strings/README.md rename to exercise/d_simple_types/README.md diff --git a/exercise/d_simple_types/src/main.rs b/exercise/d_simple_types/src/main.rs new file mode 100644 index 00000000..b4f365aa --- /dev/null +++ b/exercise/d_simple_types/src/main.rs @@ -0,0 +1,84 @@ +// Silence some warnings so they don't distract from the exercise. +#![allow(dead_code, unused_variables)] + +fn main() { + let coords: (f64, f64) = (6.3, 15.0); + // 1. Pass the two elements of the `coords` tuple as two separate arguments to the + // `print_difference` function. Use tuple indexing. + // + // The `print_difference` function is defined near the end of this file if you would like to + // look at how it is defined. + // + //print_difference( ... ); // Uncomment and finish this line + + // 2. We want to use the `print_array` function to print coords...but coords isn't an array! + // Create an array of type [f64; 2] and initialize it to contain the + // information from coords. Uncomment the print_array line and run the code. + // + //let coords_arr... // create an array literal out of parts of `coord` here + //print_array(coords_arr); // and pass it in here (this line doesn't need to change) + + let series = [1, 1, 2, 3, 5, 8, 13]; + // 3. Make the `ding` function happy by passing it the value 13 out of the `series` array. + // Use array indexing. Done correctly, `cargo run` will produce the additional output + // "Ding, you found 13!" + // + //ding(...); + + let mess = ([3, 2], 3.14, [(false, -3), (true, -100)], 5, "candy"); + // 4. Pass the `on_off` function the value `true` from the variable `mess`. Done correctly, + // `cargo run` will produce the additional output "Lights are on!" I'll get you started: + // + //on_off(mess.2 ...); + + // 5. (Part A) + // + // Uncomment the line below, run the code, and examine the output. Then go refactor the + // `print_distance` function according to the instructions in the comments inside that function. + + // print_distance(coords); +} + +// 5. (Part B) +// +// Using `z.0` and `z.1` is not nearly as nice as using `x` and `y`. Lucky for us, Rust supports +// destructuring function arguments. Try replacing `z` in the parameter list below with `(x, y)` +// and then adjust the function body below to use `x` and `y` instead of `z.0` and `z.1` +// +// You should be able to run the code again and get the output as before. +fn print_distance(z: (f64, f64)) { + println!( + "Distance to the origin is {}", + (z.0.powf(2.0) + z.1.powf(2.0)).sqrt() + ); +} + +// Challenge: +// +// Although types can often be inferred by the compiler, sometimes we write them out for clarity. +// Like we did with the `let coords: (f64, f64) = ...` declaration at the top of the `main` +// function. +// +// - Add the type annotation for the `series` variable in `main`. +// - Add the type annotation for the `mess` variable in `main`. (This may be a good example of why +// it is nice to *not* have to add the type annotation! 😆) + +fn print_difference(x: f64, y: f64) { + println!("Difference between {} and {} is {}", x, y, (x - y).abs()); +} + +fn print_array(a: [f64; 2]) { + println!("The coordinates are ({}, {})", a[0], a[1]); +} + +fn ding(x: i32) { + if x == 13 { + println!("Ding, you found 13!"); + } +} + +fn on_off(val: bool) { + if val { + println!("Lights are on!"); + } +} diff --git a/exercise/f_structs_traits/Cargo.toml b/exercise/e_control_flow/Cargo.toml similarity index 70% rename from exercise/f_structs_traits/Cargo.toml rename to exercise/e_control_flow/Cargo.toml index b184bd50..56a83ce3 100644 --- a/exercise/f_structs_traits/Cargo.toml +++ b/exercise/e_control_flow/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "f_structs_traits" +name = "e_control_flow" version = "0.1.0" edition = "2021" diff --git a/exercise/e_ownership_references/README.md b/exercise/e_control_flow/README.md similarity index 100% rename from exercise/e_ownership_references/README.md rename to exercise/e_control_flow/README.md diff --git a/exercise/e_control_flow/src/main.rs b/exercise/e_control_flow/src/main.rs new file mode 100644 index 00000000..17a3f149 --- /dev/null +++ b/exercise/e_control_flow/src/main.rs @@ -0,0 +1,70 @@ +// Silence some warnings so they don't distract from the exercise. +#![allow(unused_mut, unused_variables)] + +fn main() { + // 1. Use an unconditional `loop` to count how many times we can double `bunnies` until there + // are over 500 bunnies. (Hint: The answer is 8 times) + // + // Inside the loop: + // - Add 1 to `count` + // - Multiply `bunnies` by 2 + // - If `bunnies` is larger than 500, break out of the loop. + + let mut count = 0; + let mut bunnies = 2; + + // (write your `loop` here) + + println!( + "Bunnies doubled {} times before there were more than 500", + count + ); + + // 2. Use a `for` loop to iterate through integers from 7 to 23 *inclusive* using a range + // and add them all together (add each value to the `sum` variable). Hint: You should get 255 + + let mut sum = 0; + + // (write the `for` loop here) + + println!("The sum is {}", sum); + + // 3. Use a `while` loop to add 12 numbers to the `fives` vector. + // + // The loop should continue while `fives.len()` is less than 12. + // + // Each time through the loop: + // - Call `fives.push(number)` to push (a copy of) `number` onto the vector + // - Add 5 to `number` + // + // If you do this correctly, the vector will be [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60] + + let mut fives: Vec = vec![]; + let mut number = 5; + + // (write the `while` loop here) + + println!("Here are the first 12 multiples of 5: {:?}", fives); + + // 4. Use `if`, `else if` and `else` inside the `for` loop below to do the following: + // + // - If the number is 0, then add 7 to `total` + // - If the number is 1 or 2 then add 30 to `total` + // - If the number is anything else, subtract 5 from `total` + // + // Hint: The total should be 52 + + let mut total = 0; + let numbers = vec![0, 1, 2, 3, 4, 5]; + for number in numbers { + // (write your `if/else` expression here) + } + + println!("The total is {}", total); + + // Challenge: Change the implementation of your answers to #1-#3 as follows: + // + // - Change #1 to use `while` + // - Change #2 to use `loop` + // - Change #3 to use `for` and a range (multiply the range value by 5 inside your loop before +} diff --git a/exercise/d_control_flow_strings/Cargo.toml b/exercise/f_strings/Cargo.toml similarity index 65% rename from exercise/d_control_flow_strings/Cargo.toml rename to exercise/f_strings/Cargo.toml index 133a536d..fa5e2011 100644 --- a/exercise/d_control_flow_strings/Cargo.toml +++ b/exercise/f_strings/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "d_control_flow_strings" +name = "f_strings" version = "0.1.0" edition = "2021" diff --git a/exercise/f_strings/src/main.rs b/exercise/f_strings/src/main.rs new file mode 100644 index 00000000..1bd46069 --- /dev/null +++ b/exercise/f_strings/src/main.rs @@ -0,0 +1,46 @@ +// Silence some warnings so they don't distract from the exercise. +#![allow(unused_mut, unused_assignments)] + +// Q. What's the difference between a string literal and a borrowed string slice? +// +// A. A string literal is what is written in your source code. e.g. "this is a string literal", +// while a borrowed strings slice (&str) is the *type* of the string literal. So: +// +// let my_name: &str = "Nathan"; +// +// The variable my_name is a borrowed string slice, initialized by the string literal "Nathan". + +fn main() { + // 1. Using unicode escape codes, use println to print out a sparkles emoji (codepoint 2728). + + // println!( ... ); + + // 2. Uncomment the commented line below and set the value of `favorite` to the emoji "🍓" + // (codepoint 1f353). + // + // - You can type (or copy-and-paste) in the strawberry emoji, or use unicode escape codes. + // - Use .to_string() to convert the string literal into a String. + + let mut favorite = String::new(); + // favorite = ... + if favorite != "" { + println!("Everyone's favorite fruit is: {favorite}"); + } + + // 3. Uncomment the code below. Using newline escape codes, complete the string literal so it + // prints out: + // + // Now is + // the time + // for all + // great men + + // let saying = "Now ... + // println!("{saying}"); + + // Challenge: Change the string literal in #3 so that it: + // + // - Is a multiline string (uses real newlines instead of newline escape codes) + // - Is indented properly with the code + // - Still outputs the exact same text, without any leading spaces +} diff --git a/exercise/f_structs_traits/src/main.rs b/exercise/f_structs_traits/src/main.rs deleted file mode 100644 index b87ffb66..00000000 --- a/exercise/f_structs_traits/src/main.rs +++ /dev/null @@ -1,58 +0,0 @@ -// 1. Define a trait named `Bite` -// -// Define a single required method, `fn bite(self: &mut Self)`. We will call this method when we -// want to bite something. Once this trait is defined, you should be able to run the program with -// `cargo run` without any errors. -// -// trait Bite... - - -// 2. Now create a struct named Grapes with a field that tracks how many grapes are left. If you -// need a hint, look at how it was done for Carrot at the bottom of this file (you should probably -// use a different field, though). -// -// #[derive(Debug)] // include this line right before your struct definition -// struct Grapes... - - -// 3. Implement Bite for Grapes. When you bite a Grapes, subtract 1 from how many grapes are left. -// If you need a hint, look at how it was done for Carrot at the bottom of this file. -// -// impl Bite for... - - -fn main() { - // Once you finish #1 above, this part should work. - let mut carrot = Carrot { percent_left: 100.0 }; - carrot.bite(); - println!("I take a bite: {:?}", carrot); - - // 4. Uncomment and adjust the code below to match how you defined your - // Grapes struct. - // - //let mut grapes = Grapes { amount_left: 100 }; - //grapes.bite(); - //println!("Eat a grape: {:?}", grapes); - - // Challenge: Uncomment the code below. Create a generic `bunny_nibbles` - // function that: - // - takes a mutable reference to any type that implements Bite - // - calls `.bite()` several times - // Hint: Define the generic type between the function name and open paren: - // fn function_name(...) - // - //bunny_nibbles(&mut carrot); - //println!("Bunny nibbles for awhile: {:?}", carrot); -} - -#[derive(Debug)] // This enables using the debugging format string "{:?}" -struct Carrot { - percent_left: f32, -} - -impl Bite for Carrot { - fn bite(self: &mut Self) { - // Eat 20% of the remaining carrot. It may take awhile to eat it all... - self.percent_left *= 0.8; - } -} \ No newline at end of file diff --git a/exercise/g_collections_enums/src/main.rs b/exercise/g_collections_enums/src/main.rs deleted file mode 100644 index 72493293..00000000 --- a/exercise/g_collections_enums/src/main.rs +++ /dev/null @@ -1,77 +0,0 @@ -// Silence some warnings that could distract from the exercise -#![allow(unused_variables, unused_mut, dead_code)] - -// Someone is shooting arrows at a target. We need to classify the shots. -// -// 1a. Create an enum called `Shot` with variants: -// - `Bullseye` -// - `Hit`, containing the distance from the center (an f64) -// - `Miss` -// -// You will need to complete 1b as well before you will be able to run this program successfully. - -impl Shot { - // Here is a method for the `Shot` enum you just defined. - fn points(self) -> i32 { - // 1b. Implement this method to convert a Shot into points - // - return 5 points if `self` is a `Shot::Bullseye` - // - return 2 points if `self` is a `Shot::Hit(x)` where x < 3.0 - // - return 1 point if `self` is a `Shot::Hit(x)` where x >= 3.0 - // - return 0 points if `self` is a Miss - } -} - -fn main() { - // Simulate shooting a bunch of arrows and gathering their coordinates on the target. - let arrow_coords: Vec = get_arrow_coords(5); - let mut shots: Vec = Vec::new(); - - // 2. For each coord in arrow_coords: - // - // A. Call `coord.print_description()` - // B. Append the correct variant of `Shot` to the `shots` vector depending on the value of - // `coord.distance_from_center()` - // - Less than 1.0 -- `Shot::Bullseye` - // - Between 1.0 and 5.0 -- `Shot::Hit(value)` - // - Greater than 5.0 -- `Shot::Miss` - - - let mut total = 0; - // 3. Finally, loop through each shot in shots and add its points to total - - println!("Final point total is: {}", total); -} - -// A coordinate of where an Arrow hit -#[derive(Debug)] -struct Coord { - x: f64, - y: f64, -} - -impl Coord { - fn distance_from_center(&self) -> f64 { - (self.x.powf(2.0) + self.y.powf(2.0)).sqrt() - } - fn print_description(&self) { - println!( - "coord is {:.1} away, at ({:.1}, {:.1})", - self.distance_from_center(), - self.x, - self.y); - } - -} - -// Generate some random coordinates -fn get_arrow_coords(num: u32) -> Vec { - let mut coords: Vec = Vec::new(); - for _ in 0..num { - let coord = Coord { - x: (rand::random::() - 0.5) * 12.0, - y: (rand::random::() - 0.5) * 12.0, - }; - coords.push(coord); - } - coords -} \ No newline at end of file diff --git a/exercise/e_ownership_references/Cargo.toml b/exercise/g_ownership_references/Cargo.toml similarity index 100% rename from exercise/e_ownership_references/Cargo.toml rename to exercise/g_ownership_references/Cargo.toml diff --git a/exercise/f_structs_traits/README.md b/exercise/g_ownership_references/README.md similarity index 100% rename from exercise/f_structs_traits/README.md rename to exercise/g_ownership_references/README.md diff --git a/exercise/e_ownership_references/src/main.rs b/exercise/g_ownership_references/src/main.rs similarity index 100% rename from exercise/e_ownership_references/src/main.rs rename to exercise/g_ownership_references/src/main.rs diff --git a/exercise/h_closures_threads/src/main.rs b/exercise/h_closures_threads/src/main.rs deleted file mode 100644 index da752938..00000000 --- a/exercise/h_closures_threads/src/main.rs +++ /dev/null @@ -1,100 +0,0 @@ -// Silence some warnings so they don't distract from the exercise. -#![allow(dead_code, unused_imports, unused_variables)] -use crossbeam::channel; -use std::thread; -use std::time::Duration; - -fn expensive_sum(v: Vec) -> i32 { - pause_ms(500); - println!("Child thread: just about finished"); - // 1a. Between the .iter() and the .sum() add a .filter() with a closure to keep any even - // number (`x % 2` will be 0 for even numbers). - // 1b. Between the .filter() and the .sum() add a .map() with a closure to square the values - // (multiply them by themselves) - // - // In the closures for both the .filter() and .map() the argument will be a reference, so you'll - // either need to dereference the argument once in the parameter list like this: `|&x|` or you - // will need to dereference it each time you use it in the expression like this: `*x` - v.iter() - // .filter() goes here - // .map() goes here - .sum() -} - -fn pause_ms(ms: u64) { - thread::sleep(Duration::from_millis(ms)); -} - -fn main() { - let my_vector = vec![2, 5, 1, 0, 4, 3]; - - // 2. Spawn a child thread and have it call `expensive_sum(my_vector)`. Store the returned - // join handle in a variable called `handle`. Once you've done this you should be able to run - // the code and see the Child thread output in the middle of the main thread's letters - // - //let handle = ... - - // While the child thread is running, the main thread will also do some work - for letter in vec!["a", "b", "c", "d", "e", "f"] { - println!("Main thread: Letter {}", letter); - pause_ms(200); - } - - // 3. Let's retrieve the value returned by the child thread once it has exited. Using the - // `handle` variable you stored the join handle in earlier, call .join() to wait for the thread - // to exit with a `Result`. Get the i32 out of the result and store it in a `sum` - // variable. Uncomment the println. If you did 1a and 1b correctly, the sum should be 20. - // - //let sum = - //println!("The child thread's expensive sum is {}", sum); - - // Time for some fun with threads and channels! Though there is a primitive type of channel - // in the std::sync::mpsc module, I recommend always using channels from the crossbeam crate, - // which is what we will use here. - // - // 4. Uncomment the block comment below (Find and remove the `/*` and `*/`). Examine how the - // flow of execution works. Once you understand it, alter the values passed to the `pause_ms()` - // calls so that both the "Thread B" outputs occur before the "Thread A" outputs. - - /* - let (tx, rx) = channel::unbounded(); - // Cloning a channel makes another variable connected to that end of the channel so that you can - // send it to another thread. - let tx2 = tx.clone(); - - let handle_a = thread::spawn(move || { - pause_ms(0); - tx2.send("Thread A: 1").unwrap(); - pause_ms(200); - tx2.send("Thread A: 2").unwrap(); - }); - - pause_ms(100); // Make sure Thread A has time to get going before we spawn Thread B - - let handle_b = thread::spawn(move || { - pause_ms(0); - tx.send("Thread B: 1").unwrap(); - pause_ms(200); - tx.send("Thread B: 2").unwrap(); - }); - - // Using a Receiver channel as an iterator is a convenient way to get values until the channel - // gets closed. A Receiver channel is automatically closed once all Sender channels have been - // closed. Both our threads automatically close their Sender channels when they exit and the - // destructors for the channels get automatically called. - for msg in rx { - println!("Main thread: Received {}", msg); - } - - // Join the child threads for good hygiene. - handle_a.join().unwrap(); - handle_b.join().unwrap(); - */ - - // Challenge: Make two child threads and give them each a receiving end to a channel. From the - // main thread loop through several values and print each out and then send it to the channel. - // On the child threads print out the values you receive. Close the sending side in the main - // thread by calling `drop(tx)` (assuming you named your sender channel variable `tx`). Join - // the child threads. - println!("Main thread: Exiting.") -} diff --git a/exercise/z_final_project/Cargo.toml b/exercise/h_structs/Cargo.toml similarity index 52% rename from exercise/z_final_project/Cargo.toml rename to exercise/h_structs/Cargo.toml index 1d0c2e30..392ff202 100644 --- a/exercise/z_final_project/Cargo.toml +++ b/exercise/h_structs/Cargo.toml @@ -1,8 +1,6 @@ [package] -name = "mirage" +name = "h_structs" version = "0.1.0" edition = "2021" [dependencies] -image = "0.24.3" -num-complex = "0.4.2" diff --git a/exercise/h_structs/src/main.rs b/exercise/h_structs/src/main.rs new file mode 100644 index 00000000..1f8e1454 --- /dev/null +++ b/exercise/h_structs/src/main.rs @@ -0,0 +1,94 @@ +// Silence some warnings so they don't distract from the exercise. +#![allow(dead_code, unused_mut)] + +// 1. Create a struct named `Polygon` with the fields and their types listed below. Then build the +// program with `cargo build` to ensure you don't have any syntax errors. +// +// - name - String +// - sides - u32 +// - visible - bool + +// struct Polygon ... + +// 2. Create an implementation block for the `Polygon` struct. +// +// In the implementation block define an associated function named `new` that: +// - accepts an argument `name` of type `String` +// - returns a `Polygon` (you may use `Self` as an alias for `Polygon` inside of the `impl` block) +// - with `name` set to the value from the `name` argument. +// - with `sides` set to `3` +// - with `visible` set to `true` +// +// NOTE: Associated functions do NOT take a form a `self` as their first argument (that would turn +// the function into a method) +// +// Then build the program with `cargo build` to ensure you don't have any syntax errors. + +// impl Polygon ... + +fn main() { + // 3. Create a new, mutable polygon variable by calling the Polygon's `new` associated function. + // + // - Use the scope operator `::` to access an associated function of a struct. + // - Use the name "George". Remember to convert the string literal to a String! + // + // Then uncomment and run the code below to see a message about the polygon. + + // let mut polygon = ... + // println!( + // "I see a {}-sided polygon named {}!", + // polygon.sides, polygon.name + // ); + + // 4. In the `impl Polygon` block above: + // + // - Add a method named `shape` which + // - takes an immutable reference to self + // - returns a String + // - depending on the value of the `sides` field returns the following strings: + // - "triangle" - for 3 sides + // - "square" - for 4 sides + // - "pentagon" - for 5 sides + // - "polygon" - for any other number of sides + // + // Then uncomment and run the code below. + + // println!( + // "The polygon named {} is a {}", + // polygon.name, + // polygon.shape() + // ); + + // 5. In the `impl Polygon` block above: + // + // - Add a method named `increment_sides` that + // - takes a mutable reference to self + // - returns nothing + // - adds 1 to the `sides` field + // + // Then uncomment and run the code below. + + // for _ in 0..3 { + // polygon.increment_sides(); + // println!( + // "The polygon now has {} sides and is the shape of a {}", + // polygon.sides, + // polygon.shape() + // ); + // } + + // Challenge: Move the `Polygon` struct and impl blocks to lib.rs and put `pub` in front of the + // fields, methods, and associated function that need to be public. Then add `use` statements to + // this file so that the program will run and produce the same output as before. + + // Challenge 2: Make the Polygon's `sides` field private by removing the `pub`. Add a method + // that you can call to get the value of the `sides` field without directly accessing it. + // "Getter" methods for `Copy`† types are typically named the same as their private field, e.g. + // `pub fn sides(...)` would be the method to return the value of the private `sides` field. + // + // Modify the code in this file to use the new method instead of accessing the `sides` field + // directly. You should be able to run the program and still get the same output. + // + // †Copy types (types that implement the Copy trait) are briefly described in the Traits lesson, + // and more thoroughly explained in Ultimate Rust 2. +} diff --git a/exercise/i_traits/Cargo.toml b/exercise/i_traits/Cargo.toml new file mode 100644 index 00000000..9276ba19 --- /dev/null +++ b/exercise/i_traits/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "i_traits" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/exercise/g_collections_enums/README.md b/exercise/i_traits/README.md similarity index 100% rename from exercise/g_collections_enums/README.md rename to exercise/i_traits/README.md diff --git a/exercise/i_traits/src/main.rs b/exercise/i_traits/src/main.rs new file mode 100644 index 00000000..527c5a37 --- /dev/null +++ b/exercise/i_traits/src/main.rs @@ -0,0 +1,89 @@ +// Silence some warnings so they don't distract from the exercise. +#![allow(dead_code)] + +// 1a: Create a `Colorful` trait with a single `color` method which takes an immutable reference to +// self and returns a String describing that item's color. +// +// You must also complete 1b before the code will compile. + +// trait Colorful ... + +// 1b. Implement the `Colorful` trait for the `Hat` struct: +// +// The `colorful` method of the `Colorful` trait implementation for the `Hat` struct should return +// the following String values: +// - "red" if the size is 0 through 5 +// - "green" - if the size is 6 or 7 +// - "blue" - for any other size +// +// The code should compile once 1b is complete, but there will be no output until 1c is completed. + +struct Hat { + size: i32, +} + +// impl Colorful for Hat ... + +fn main() { + // 1c. Uncomment and run the code below. If you correctly implemented Colorful for Hat, then + // the order of the colors in the output will be red, green, and blue. + + // let small_hat = Hat { size: 2 }; + // let medium_hat = Hat { size: 7 }; + // let large_hat = Hat { size: 100 }; + // describe_three_hats(&small_hat, &medium_hat, &large_hat); + + // 2. Implement the Colorful trait for the type i32. The `colorful` method for an i32 should + // return these String values: + // - "orange" - If the number is even (see hint) + // - "purple" - If the number is odd + // + // Then uncomment and run the code below. + // + // Hint: You may want to use the `is_even` function (see the bottom of this file). + + // println!("4 is {}", 4.color()); + // println!("5 is {}", 5.color()); + + // 3. Let's replace the is_even function with a trait implementation! + // + // - Comment out the is_even function at the bottom of this file so you can't use it anymore. + // - Create a trait named `EvenOdd` with a method `is_even`. It should take an immutable + // reference to self and return a bool. + // - Implement EvenOdd for i32. You can copy-and-paste the logic from the is_even function that + // you commented out earlier. + // - Refactor the `colorful` method for `i32` to use the is_even method. + // + // Then you should be able to run the code without any changes to the output. + + // Challenge: Write a generic function named `fortune` that takes anything that implements the + // Colorful trait and prints out the color in some message (for example: "The color I see in + // your future is ..."). Then uncomment and run the code below. + // + // Hint: There's a bit of commented-out code below the main function to help you get started. + + // fortune(small_hat); + // fortune(2); +} + +// fn fortune(... + +// A function used by some provided code. +fn describe_three_hats(hat1: &Hat, hat2: &Hat, hat3: &Hat) { + for hat in [hat1, hat2, hat3] { + let largeness = if hat.size < 3 { + "small" + } else if hat.size < 9 { + "medium" + } else { + "large" + }; + println!("The {} hat is {}", largeness, hat.color()); + } +} + +// You can use this function to check if a number is even (true) or odd (false). +// You should comment out this function for #3. +fn is_even(number: i32) -> bool { + number % 2 == 0 +} diff --git a/exercise/j_collections/Cargo.toml b/exercise/j_collections/Cargo.toml new file mode 100644 index 00000000..bf29b117 --- /dev/null +++ b/exercise/j_collections/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "j_collections" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/exercise/z_final_project/README.md b/exercise/j_collections/README.md similarity index 100% rename from exercise/z_final_project/README.md rename to exercise/j_collections/README.md diff --git a/exercise/j_collections/src/main.rs b/exercise/j_collections/src/main.rs new file mode 100644 index 00000000..0365c10f --- /dev/null +++ b/exercise/j_collections/src/main.rs @@ -0,0 +1,51 @@ +fn main() { + // Here are some String variables to use. There are many ways to create a String! + let item = String::from("socks"); + let animal = "fox".to_string(); + let container = "box".to_owned(); + let material = "rocks".into(); // .into() works as long as you use the value as a String later + + // 1. Create a Vec named `things` and move all of the strings above into it. You can do + // this by creating `things` and then calling the `push` method repeatedly, or by using the + // `vec!` macro. Then uncomment and run the code below. + + // let things ... + // println!("{:?}", things); // `:?` means "the debug representation" + + // 2. Print out the length of the `things` vector using the `len` method. + + // println!("things has a length of {}", ...); + + // 3. We want to use the `animal` variable in the (commented-out) code below, but we cannot + // because the value has been moved into `things`. Uncomment the code below and change it to use + // array indexing (with square brackets []) to index into `things` to access the `fox` String. + + // println!("What does the {} say?", animal); // get the value from `things` instead of `animal` + + // 4. Sort `things` by calling the `sort` method. The variable needs to be mutable for this to + // compile without errors. Then uncomment and run the code below. + + // println!("Sorted values: {things:?}"); // variables can go inside the curly braces + + // 5. Use a `for` loop to print out each item in `things`. It is okay to consume `things`, since + // we won't be using it any more after this. + + // for ... + + // Challenge: Create a vector named `buffer` containing 1024 zeroes using the `vec!` macro. This + // should easily fit on one line without wrapping. + + // let buffer = ... + + // Challenge 2: Use a `for` loop and array indexing to change each entry in `buffer` to be its + // index value multiplied by 2. For example: + // + // buffer[0] should be 0 + // buffer[1] should be 2 + // buffer[2] should be 4 + // etc. + // + // Then uncomment and run the code below. + + // println!("Here's a buffer full of even values: {buffer:?}"); +} diff --git a/exercise/g_collections_enums/Cargo.toml b/exercise/k_enums/Cargo.toml similarity index 72% rename from exercise/g_collections_enums/Cargo.toml rename to exercise/k_enums/Cargo.toml index 1e80bf5b..8e9ebdaa 100644 --- a/exercise/g_collections_enums/Cargo.toml +++ b/exercise/k_enums/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "g_collections_enums" +name = "k_enums" version = "0.1.0" edition = "2021" diff --git a/exercise/k_enums/README.md b/exercise/k_enums/README.md new file mode 100644 index 00000000..7ba16f91 --- /dev/null +++ b/exercise/k_enums/README.md @@ -0,0 +1,2 @@ +Please clone this repository, change to this directory, and open `src/main.rs` +and follow the instructions in the comments. diff --git a/exercise/k_enums/src/main.rs b/exercise/k_enums/src/main.rs new file mode 100644 index 00000000..f87193b6 --- /dev/null +++ b/exercise/k_enums/src/main.rs @@ -0,0 +1,124 @@ +// Silence some warnings that could distract from the exercise +#![allow(unused)] + +fn main() { + // 1. If `maybe_fruit` below is a `Some` variant, then print out the string it wraps. Use the + // Option type's `is_some` and `unwrap` methods with an `if` expression to implement the logic. + // Then run your code. You should get the output "apple". + + let maybe_fruit: Option<&str> = Some("apple"); + // if ... + + // 2. Write a function `inspect` that accepts an `Option<&str>` as an argument and does not + // return anything. Use an `if let` expression inside the function to get the value wrapped by + // the `Some` variant and print out "You passed in a {}" with the value of the string. The + // function should do nothing if the value is a `None` variant. + // + // Call the function once for `maybe_plant` and once for `maybe_food`. + // + // Then run the code. You should get one line of output about cake. + + let maybe_plant: Option<&str> = None; + let maybe_food: Option<&str> = Some("cake"); + // inspect(...); + // inspect(...); + + // 3. Write a loop that passes each number in the `numbers` vector to the `do_math` function + // and then checks the result using a `match` expression. + // + // The `do_math` function (see bottom of this file) takes an i32 as input and returns a + // `Result` whose `Ok` variant wraps a new i32, or an `Err` variant that wraps a `String` + // which is a message about the error which occurred. + // + // - If the result is an `Ok`, then print out "The result was {}", with the value of the + // wrapped number + // - If the result is an `Err`, then print out the wrapped error message string + // + // You should get one error message with a sad face, and one line with the number 100. + + let numbers = vec![0, 1]; + // for ... + + // 4. Define an enum named `Snack` with the following variants: + // + // - Apple - which contains no data + // - Cookies - which contains an unnamed tuple with a single `u8` + // - Sandwich - which contains an unnamed struct with fields `lettuce` and `cheese`. Both fields + // are the type `bool`. + // + // Then uncomment and run the code below. If you defined the enum correctly, you should get + // output about three snacks. + + // let healthy_snack = Snack::Apple; + // let sugary_snack = Snack::Cookies(18); + // let lunch = Snack::Sandwich { + // lettuce: false, + // cheese: true, + // }; + // if let Snack::Apple = healthy_snack { + // println!("The healthy snack is an apple."); + // } + // if let Snack::Cookies(num_cookies) = sugary_snack { + // println!("The sugary snack is {} cookies", num_cookies); + // } + // if let Snack::Sandwich { lettuce, cheese } = lunch { + // let lettuce_msg = if lettuce { "does" } else { "does not" }; + // let cheese_msg = if cheese { "does" } else { "does not" }; + // println!( + // "The sandwich {} have lettuce and {} have cheese.", + // lettuce_msg, cheese_msg + // ); + // } + + // 5. Create an `impl` block for the `Snack` enum and implement a method named `price` which + // takes ownership of a Snack and returns a u8 representing the price of the snack according to + // the following rules: + // + // - The price of an apple is always 5 + // - The price of cookies is 2 times the number of cookies + // - A sandwich's price starts at 10, plus 1 if lettuce is true, plus 2 if cheese is true + // + // Hint: The signature of the method is `fn price(self) -> u8` + // + // Then uncomment and run the code below. You should see three lines ending with the costs of + // $5, $36, and $12. + + // println!("An apple costs ${}", healthy_snack.price()); + // if let Snack::Cookies(number) = sugary_snack { + // println!("{} cookies costs ${}", number, sugary_snack.price()); + // } + // if let Snack::Sandwich { lettuce, cheese } = lunch { + // let lettuce_message = if lettuce { " with lettuce" } else { "" }; + // let cheese_message = if cheese { " with cheese" } else { "" }; + // println!( + // "A sandwich{}{} costs ${}", + // lettuce_message, + // cheese_message, + // lunch.price() + // ); + // } + + // Challenge 1: Implement an `is_apple` method for Snack that return a bool. Return `true` if + // the value is an `Apple` variant, and `false` otherwise. Then uncomment and run the code + // below. + + // let snacks = vec![Snack::Apple, Snack::Cookies(5), Snack::Apple]; + // for (index, snack) in snacks.iter().enumerate() { + // if snack.is_apple() { + // println!("Snack {} is an apple.", index) + // } else { + // println!("Snack {} is NOT an apple.", index) + // } + // } + + // Challenge 2: Refactor the code from (4) to put all off the variables into a vector, then loop + // through the vector and use a `match` expression instead of `if let` statements. The output + // should remain the same. +} + +fn do_math(x: i32) -> Result { + if x == 1 { + return Ok(100); + } + return Err(format!("I wanted the number 1 and you gave me a {} 🥺", x)); +} diff --git a/exercise/z_beast_game/Cargo.toml b/exercise/z_beast_game/Cargo.toml new file mode 100644 index 00000000..93d402f9 --- /dev/null +++ b/exercise/z_beast_game/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "z_beast_game" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/exercise/z_beast_game/src/main.rs b/exercise/z_beast_game/src/main.rs new file mode 100644 index 00000000..e7a11a96 --- /dev/null +++ b/exercise/z_beast_game/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/exercise/z_final_project/dyson.png b/exercise/z_final_project/dyson.png deleted file mode 100644 index 99fab391..00000000 Binary files a/exercise/z_final_project/dyson.png and /dev/null differ diff --git a/exercise/z_final_project/pens.png b/exercise/z_final_project/pens.png deleted file mode 100644 index a10f566d..00000000 Binary files a/exercise/z_final_project/pens.png and /dev/null differ diff --git a/exercise/z_final_project/src/main.rs b/exercise/z_final_project/src/main.rs deleted file mode 100644 index 08c072f8..00000000 --- a/exercise/z_final_project/src/main.rs +++ /dev/null @@ -1,229 +0,0 @@ -// FINAL PROJECT -// -// Create an image processing application. Exactly what it does and how it does -// it is up to you, though I've stubbed a good amount of suggestions for you. -// Look for comments labeled **OPTION** below. -// -// Two image files are included in the project root for your convenience: dyson.png and pens.png -// Feel free to use them or provide (or generate) your own images. -// -// Don't forget to have fun and play around with the code! -// -// Documentation for the image library is here: https://docs.rs/image/0.21.0/image/ -// -// NOTE 1: Image processing is very CPU-intensive. Your program will run *noticeably* faster if you -// run it with the `--release` flag. -// -// cargo run --release [ARG1 [ARG2]] -// -// For example: -// -// cargo run --release blur image.png blurred.png -// -// NOTE 2: This is how you parse a number from a string (or crash with a -// message). It works with any integer or float type. -// -// let positive_number: u32 = some_string.parse().expect("Failed to parse a number"); - -fn main() { - // 1. First, you need to implement some basic command-line argument handling - // so you can make your program do different things. Here's a little bit - // to get you started doing manual parsing. - // - // Challenge: If you're feeling really ambitious, you could delete this code - // and use the "clap" library instead: https://docs.rs/clap/2.32.0/clap/ - let mut args: Vec = std::env::args().skip(1).collect(); - if args.is_empty() { - print_usage_and_exit(); - } - let subcommand = args.remove(0); - match subcommand.as_str() { - // EXAMPLE FOR CONVERSION OPERATIONS - "blur" => { - if args.len() != 2 { - print_usage_and_exit(); - } - let infile = args.remove(0); - let outfile = args.remove(0); - // **OPTION** - // Improve the blur implementation -- see the blur() function below - blur(infile, outfile); - } - - // **OPTION** - // Brighten -- see the brighten() function below - - // **OPTION** - // Crop -- see the crop() function below - - // **OPTION** - // Rotate -- see the rotate() function below - - // **OPTION** - // Invert -- see the invert() function below - - // **OPTION** - // Grayscale -- see the grayscale() function below - - // A VERY DIFFERENT EXAMPLE...a really fun one. :-) - "fractal" => { - if args.len() != 1 { - print_usage_and_exit(); - } - let outfile = args.remove(0); - fractal(outfile); - } - - // **OPTION** - // Generate -- see the generate() function below -- this should be sort of like "fractal()"! - - // For everything else... - _ => { - print_usage_and_exit(); - } - } -} - -fn print_usage_and_exit() { - println!("USAGE (when in doubt, use a .png extension on your filenames)"); - println!("blur INFILE OUTFILE"); - println!("fractal OUTFILE"); - // **OPTION** - // Print useful information about what subcommands and arguments you can use - // println!("..."); - std::process::exit(-1); -} - -fn blur(infile: String, outfile: String) { - // Here's how you open an existing image file - let img = image::open(infile).expect("Failed to open INFILE."); - // **OPTION** - // Parse the blur amount (an f32) from the command-line and pass it through - // to this function, instead of hard-coding it to 2.0. - let img2 = img.blur(2.0); - // Here's how you save an image to a file. - img2.save(outfile).expect("Failed writing OUTFILE."); -} - -fn brighten(infile: String, outfile: String) { - // See blur() for an example of how to open / save an image. - - // .brighten() takes one argument, an i32. Positive numbers brighten the - // image. Negative numbers darken it. It returns a new image. - - // Challenge: parse the brightness amount from the command-line and pass it - // through to this function. -} - -fn crop(infile: String, outfile: String) { - // See blur() for an example of how to open an image. - - // .crop() takes four arguments: x: u32, y: u32, width: u32, height: u32 - // You may hard-code them, if you like. It returns a new image. - - // Challenge: parse the four values from the command-line and pass them - // through to this function. - - // See blur() for an example of how to save the image. -} - -fn rotate(infile: String, outfile: String) { - // See blur() for an example of how to open an image. - - // There are 3 rotate functions to choose from (all clockwise): - // .rotate90() - // .rotate180() - // .rotate270() - // All three methods return a new image. Pick one and use it! - - // Challenge: parse the rotation amount from the command-line, pass it - // through to this function to select which method to call. - - // See blur() for an example of how to save the image. -} - -fn invert(infile: String, outfile: String) { - // See blur() for an example of how to open an image. - - // .invert() takes no arguments and converts the image in-place, so you - // will use the same image to save out to a different file. - - // See blur() for an example of how to save the image. -} - -fn grayscale(infile: String, outfile: String) { - // See blur() for an example of how to open an image. - - // .grayscale() takes no arguments. It returns a new image. - - // See blur() for an example of how to save the image. -} - -fn generate(outfile: String) { - // Create an ImageBuffer -- see fractal() for an example - - // Iterate over the coordinates and pixels of the image -- see fractal() for an example - - // Set the image to some solid color. -- see fractal() for an example - - // Challenge: parse some color data from the command-line, pass it through - // to this function to use for the solid color. - - // Challenge 2: Generate something more interesting! - - // See blur() for an example of how to save the image -} - -// This code was adapted from https://github.com/PistonDevelopers/image -fn fractal(outfile: String) { - let width = 800; - let height = 800; - - let mut imgbuf = image::ImageBuffer::new(width, height); - - let scale_x = 3.0 / width as f32; - let scale_y = 3.0 / height as f32; - - // Iterate over the coordinates and pixels of the image - for (x, y, pixel) in imgbuf.enumerate_pixels_mut() { - // Use red and blue to be a pretty gradient background - let red = (0.3 * x as f32) as u8; - let blue = (0.3 * y as f32) as u8; - - // Use green as the fractal foreground (here is the fractal math part) - let cx = y as f32 * scale_x - 1.5; - let cy = x as f32 * scale_y - 1.5; - - let c = num_complex::Complex::new(-0.4, 0.6); - let mut z = num_complex::Complex::new(cx, cy); - - let mut green = 0; - while green < 255 && z.norm() <= 2.0 { - z = z * z + c; - green += 1; - } - - // Actually set the pixel. red, green, and blue are u8 values! - *pixel = image::Rgb([red, green, blue]); - } - - imgbuf.save(outfile).unwrap(); -} - -// **SUPER CHALLENGE FOR LATER** - Let's face it, you don't have time for this during class. -// -// Make all of the subcommands stackable! -// -// For example, if you run: -// -// cargo run infile.png outfile.png blur 2.5 invert rotate 180 brighten 10 -// -// ...then your program would: -// - read infile.png -// - apply a blur of 2.5 -// - invert the colors -// - rotate the image 180 degrees clockwise -// - brighten the image by 10 -// - and write the result to outfile.png -// -// Good luck!