├── .gitignore ├── .vscode └── launch.json ├── Cargo.lock ├── Cargo.toml ├── day1 ├── day_1_wrap.md ├── hour1 │ ├── class_intro.md │ ├── hello_library.md │ ├── hello_world.md │ ├── hour_1_wrap.md │ ├── setup_ide.md │ ├── setup_rust.md │ ├── simple_login_test.md │ └── workspaces.md ├── hour2 │ ├── enums.md │ ├── hour_2_wrap.md │ └── structs.md ├── hour3 │ ├── hashing.md │ ├── hashmaps.md │ ├── hour_3_wrap.md │ ├── serialization.md │ └── vectors.md ├── hour4 │ └── cli.md └── readme.md ├── day2 ├── hour1 │ ├── day1_hour2_wrap.md │ ├── day2_intro.md │ ├── documentation.md │ ├── errors.md │ └── modules.md ├── hour2 │ ├── borrow_checker.md │ ├── intro.md │ ├── lifetimes.md │ ├── oop.md │ └── raii.md ├── hour3 │ ├── atomic.md │ ├── count_primes.md │ ├── data_race.md │ ├── rayon.md │ └── shared.md ├── hour4 │ ├── channels.md │ ├── tcp_server.md │ └── tokio.md └── readme.md ├── day3 ├── hour1 │ ├── globals.md │ ├── new_types.md │ ├── sync.md │ └── traits.md ├── hour2 │ ├── constants.md │ ├── generic_complex.md │ └── generic_data.md └── readme.md ├── day4 ├── hour1 │ ├── benchmarks.md │ ├── benchmarks2.md │ ├── dashmap.md │ ├── feature_flags.md │ ├── macros.md │ ├── netbench.md │ ├── nostd.md │ ├── parking_lot.md │ ├── rocket.md │ ├── safety.md │ └── tcp_login.md └── readme.md ├── images ├── .~lock.Timing Estimate.ods# ├── BreakpointsEverywhere.png ├── ColoredUsers.png ├── ExpandMacro.png ├── FunctionDocs.png ├── Hands-on Rust.png ├── ModuleDocs.png ├── ModuleErrors2.png ├── ModuleErrorsVsCode.png ├── PuttyEcho.png ├── Rust Brain Teasers.png ├── RustAnalyzerClippy.png ├── RustUp.png ├── Timing Estimate.ods ├── VectorGrowth.png └── ardanlabs-logo.png ├── readme.md └── src ├── auth_enum ├── Cargo.toml └── src │ └── lib.rs ├── auth_enum2 ├── Cargo.toml └── src │ └── lib.rs ├── auth_enum3 ├── Cargo.toml └── src │ └── lib.rs ├── auth_enum_exe ├── Cargo.toml └── src │ └── main.rs ├── auth_enum_exe3 ├── Cargo.toml └── src │ └── main.rs ├── auth_enum_fn ├── Cargo.toml └── src │ └── lib.rs ├── auth_enum_fn_exe ├── Cargo.toml └── src │ └── main.rs ├── auth_hashmap ├── Cargo.toml └── src │ └── lib.rs ├── auth_hashmap_exe ├── Cargo.toml └── src │ └── main.rs ├── auth_if ├── Cargo.toml └── src │ └── lib.rs ├── auth_if_exe ├── Cargo.toml └── src │ └── main.rs ├── auth_json ├── Cargo.toml └── src │ └── lib.rs ├── auth_json_exe ├── Cargo.toml ├── src │ └── main.rs └── users.json ├── auth_passwords ├── Cargo.toml └── src │ └── lib.rs ├── auth_passwords_exe ├── Cargo.toml ├── src │ └── main.rs └── users.json ├── auth_struct ├── Cargo.toml └── src │ └── lib.rs ├── auth_struct_exe ├── Cargo.toml └── src │ └── main.rs ├── auth_userman ├── Cargo.toml └── src │ └── lib.rs ├── auth_vec ├── Cargo.toml └── src │ └── lib.rs ├── auth_vec_exe ├── Cargo.toml └── src │ └── main.rs ├── bench ├── Cargo.toml └── src │ └── lib.rs ├── borrow_iter ├── Cargo.toml └── src │ └── main.rs ├── borrow_move1 ├── Cargo.toml └── src │ └── main.rs ├── borrow_move2 ├── Cargo.toml └── src │ └── main.rs ├── borrow_move3 ├── Cargo.toml └── src │ └── main.rs ├── borrow_move4 ├── Cargo.toml └── src │ └── main.rs ├── borrow_move5 ├── Cargo.toml └── src │ └── main.rs ├── borrow_oops ├── Cargo.toml └── src │ └── main.rs ├── borrow_oops2 ├── Cargo.toml └── src │ └── main.rs ├── borrow_oops3 ├── Cargo.toml └── src │ └── main.rs ├── count_primes ├── Cargo.toml └── src │ └── main.rs ├── count_primes_atomic ├── Cargo.toml └── src │ └── main.rs ├── count_primes_atomic_many ├── Cargo.toml └── src │ └── main.rs ├── count_primes_bad ├── Cargo.toml └── src │ └── main.rs ├── count_primes_rayon ├── Cargo.toml └── src │ └── main.rs ├── count_primes_rayon2 ├── Cargo.toml └── src │ └── main.rs ├── count_primes_shared ├── Cargo.toml └── src │ └── main.rs ├── count_primes_shared2 ├── Cargo.toml └── src │ └── main.rs ├── cpp └── Primes │ ├── .vs │ └── Primes │ │ └── v17 │ │ ├── .suo │ │ ├── Browse.VC.db │ │ └── ipch │ │ └── AutoPCH │ │ ├── d63b32b54c0f111b │ │ └── PRIMES.ipch │ │ └── e71747352f44b5a9 │ │ └── PRIMES.ipch │ ├── Primes.cpp │ ├── Primes.sln │ ├── Primes.vcxproj │ ├── Primes.vcxproj.filters │ ├── Primes.vcxproj.user │ └── x64 │ └── Release │ ├── Primes.exe │ ├── Primes.exe.recipe │ ├── Primes.iobj │ ├── Primes.ipdb │ ├── Primes.log │ ├── Primes.obj │ ├── Primes.pdb │ ├── Primes.tlog │ ├── CL.command.1.tlog │ ├── CL.read.1.tlog │ ├── CL.write.1.tlog │ ├── Primes.lastbuildstate │ ├── Primes.write.1u.tlog │ ├── link.command.1.tlog │ ├── link.read.1.tlog │ └── link.write.1.tlog │ ├── Primes.vcxproj.FileListAbsolute.txt │ ├── vc143.pdb │ └── vcpkg.applocal.log ├── dashmap ├── Cargo.toml └── src │ └── main.rs ├── docs ├── Cargo.toml └── src │ ├── lib.rs │ ├── login_action │ ├── action.rs │ ├── denied_reason.rs │ ├── mod.rs │ └── role.rs │ └── user.rs ├── docs_exe ├── Cargo.toml └── src │ └── main.rs ├── errors1 ├── Cargo.toml └── src │ └── main.rs ├── errors2 ├── Cargo.toml └── src │ └── main.rs ├── errors3 ├── Cargo.toml └── src │ └── main.rs ├── file_lock ├── Cargo.toml └── src │ └── main.rs ├── flags_exe ├── Cargo.toml └── src │ └── main.rs ├── flags_lib ├── Cargo.toml └── src │ └── lib.rs ├── generic_data ├── Cargo.toml └── src │ └── main.rs ├── generic_data_complex ├── Cargo.toml └── src │ └── main.rs ├── globals ├── Cargo.toml └── src │ └── main.rs ├── hello_as_a_service ├── Cargo.toml └── src │ └── lib.rs ├── hello_auth ├── Cargo.toml └── src │ └── lib.rs ├── hello_login_system ├── Cargo.toml └── src │ └── main.rs ├── hello_service_exe ├── Cargo.toml └── src │ └── main.rs ├── hello_tokio ├── Cargo.toml └── src │ └── main.rs ├── lifetime_func ├── Cargo.toml └── src │ └── main.rs ├── macros ├── Cargo.toml └── src │ └── main.rs ├── main.rs ├── modules1 ├── Cargo.toml └── src │ └── lib.rs ├── modules1_exe ├── Cargo.toml └── src │ └── main.rs ├── modules2 ├── Cargo.toml └── src │ ├── lib.rs │ ├── login_action │ ├── action.rs │ ├── denied_reason.rs │ ├── mod.rs │ └── role.rs │ └── user.rs ├── rayon_threads ├── Cargo.toml └── src │ └── main.rs ├── rc_bench ├── Cargo.toml └── src │ ├── atomic_rc_cat.rs │ ├── cat_store.rs │ ├── cat_vec.rs │ ├── main.rs │ └── rc_cat.rs ├── rocket ├── Cargo.toml └── src │ └── main.rs ├── rocket2 ├── Cargo.toml ├── login.html └── src │ └── main.rs ├── tcp_login_server ├── Cargo.toml ├── src │ └── main.rs └── users.json ├── tcp_login_server_bench ├── Cargo.toml └── src │ └── main.rs ├── thread_channels ├── Cargo.toml └── src │ └── main.rs ├── tokio_channels ├── Cargo.toml └── src │ └── main.rs ├── tokio_channels2 ├── Cargo.toml └── src │ └── main.rs ├── tokio_rpc ├── Cargo.toml └── src │ └── main.rs ├── tokio_tcp ├── Cargo.toml └── src │ └── main.rs └── userman ├── Cargo.toml ├── src └── main.rs └── users.json /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug", 11 | "program": "${workspaceFolder}/", 12 | "args": [], 13 | "cwd": "${workspaceFolder}" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /day1/day_1_wrap.md: -------------------------------------------------------------------------------- 1 | # Day 1 Wrap-Up 2 | 3 | You've learned a *lot* on your first day: 4 | 5 | * You setup and updated Rust. 6 | * You setup Visual Studio Code for Rust development. 7 | * You progressed through "hello world" and "hello library". 8 | * You combined projects with workspaces. 9 | * You've learned the power of Rust's enumeration and pattern matching system. 10 | * You've grouped data together in structures, and implemented functions for structures and enumerations. 11 | * You learned about arrays and iterators, vectors and hashmaps. 12 | * You serialized and de-serialized data with `serde`, in JSON format. 13 | * You learned to use the `sha2` crate to hash passwords. 14 | * You used `clap` to build a full-scale command-line program for managing your user file. 15 | 16 | That's enough Rust to get you up and running on a lot of projects. 17 | 18 | Tomorrow, we're going to talk about: 19 | 20 | * Modules & Visibility 21 | * Documentation 22 | * Error handling, and dealing with `unwrap`. 23 | * The borrow checker and lifetimes. 24 | * RAII - Resource Acquisition is Allocation. 25 | * Fearless Concurrency: 26 | * How Rust protects you from Data Races 27 | * Divide work into threads 28 | * Use Rayon to quickly parallelize CPU-heavy tasks 29 | * Asynchronous Concurrency: 30 | * Using `tokio` to make a TCP server. 31 | * Green threads and blocking 32 | * Sending and receiving data with channels. 33 | 34 | *Have a good night, and see you tomorrow.* -------------------------------------------------------------------------------- /day1/hour1/class_intro.md: -------------------------------------------------------------------------------- 1 | # Class Introduction 2 | 3 | * Welcome to *Ultimate Rust: Foundations*. 4 | * This is a four-day class, taking you new to Rust (but probably familiar with other languages) to proficient in day-to-day Rust. 5 | * Each day will be divided into four hours, with a 10-12 minute break at the end of each hour. 6 | * Help me customize the class for you - please indicate your experience level with Rust in the Zoom poll I've setup. 7 | 8 | ## About the Trainer 9 | 10 | Herbert has been developing software professionally for more than 20 years. Starting with BASIC, Pascal, and then moving onto C and C++, Herbert has developed custom applications ranging from web-server filters to games. Herbert is the author of Hands-on Rust and Rust Brain Teasers. 11 | 12 | | Book | | Publisher E-Book | Amazon | 13 | |------|-| -----------------|--------| 14 | | Hands-on Rust | ![](/images/Hands-on%20Rust.png) | [PragProg Page](https://pragprog.com/titles/hwrust/hands-on-rust/) | [Amazon Page](https://www.amazon.com/Hands-Rust-Effective-Learning-Development/dp/1680508164) | 15 | | Rust Brain Teasers | ![](/images/Rust%20Brain%20Teasers.png) | [PragProg Page](https://pragprog.com/titles/hwrustbrain/rust-brain-teasers/) | [Amazon Page](https://www.amazon.com/Rust-Brain-Teasers-Pragmatic-Programmers/dp/1680509179) | 16 | 17 | ## Resources 18 | 19 | I recommend bookmarking the following resources: 20 | 21 | * [The Rust Programming Language](https://doc.rust-lang.org/book/) 22 | * [Rust by Example](https://doc.rust-lang.org/rust-by-example/) 23 | * [Rust Standard Library Documentation](https://doc.rust-lang.org/std/) 24 | 25 | > The outline for this class is available: [https://github.com/thebracket/ArdanUltimateRustFoundations/](https://github.com/thebracket/ArdanUltimateRustFoundations/) 26 | 27 | ## Topics We'll Cover 28 | 29 | * Today (Day 1), we're going to take a quick march through the basics of Rust. 30 | * Getting your environment setup. 31 | * "Hello World" - and everything `cargo init` did without telling you. 32 | * Working with Workspaces 33 | * "Hello Library" - sharing code. 34 | * Unit Testing. 35 | * Options, Enumerations, and Structures. 36 | * Arrays, Vectors and HashMaps. 37 | * Introduction to Iterators. 38 | * Dependencies. 39 | * Serialization. 40 | * CLI Application Development. 41 | 42 | TODO - MORE -------------------------------------------------------------------------------- /day1/hour1/hour_1_wrap.md: -------------------------------------------------------------------------------- 1 | # Hour 1 Wrap 2 | 3 | In the first hour, you've covered a lot of ground learning Rust Foundations. You've: 4 | 5 | * Setup Rust. 6 | * Setup your IDE (Integrated Development Environment). 7 | * Created a "hello world" application. 8 | * Built a workspace. 9 | * Created a Rust library. 10 | * Learned to unit test libraries. 11 | * Created functions. 12 | * Learned about the two types of String. 13 | * Received input from the keyboard. 14 | * Used boolean logic and `if` statements for program flow. 15 | 16 | # TIME FOR A BREAK 17 | # COME BACK IN 12 MINUTES TO CONTINUE 18 | ## After the break, we're going to start to learn about data types. -------------------------------------------------------------------------------- /day1/hour1/setup_ide.md: -------------------------------------------------------------------------------- 1 | **This is some basic Visual Studio Code setup. If you're using a different IDE, skip this. The goal is to ensure that students see the same thing as the instructor.** 2 | 3 | # Setup your Integrated Development Environment 4 | 5 | > While teaching, the instructor will use [Microsoft Visual Studio Code](https://code.visualstudio.com/download). It's free, runs the same on Windows, Mac and Linux, and integrates well with a Rust environment. 6 | 7 | ## Extensions You Need 8 | 9 | > > You should have Rust Analyzer installed as a pre-requesite. If you don't, let me know and I'll help at the first break. 10 | 11 | * [Rust Analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) 12 | * Provides everything from syntax highlighting to inline error checking, macro expansion and debugger integration. 13 | * Part of the core RustUp distribution. 14 | * Regularly Updated. 15 | * [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb) 16 | * Integrates well with Rust-Analyzer. 17 | * Provides inline debugging. 18 | * *This is optional for this class, but you probably want it anyway* 19 | 20 | ## Extensions You Don't Need - But Probably Want 21 | 22 | * [Crates](https://marketplace.visualstudio.com/items?itemName=serayuzgur.crates) 23 | * Let's you know when dependencies are outdated. 24 | * [Better TOML](https://marketplace.visualstudio.com/items?itemName=bungcip.better-toml) 25 | * Makes editing Rust's TOML configuration easier 26 | 27 | ## Some Configuration Tweaks 28 | 29 | > Live demo 30 | 31 | ### Enable Breakpoints Everywhere 32 | * Open Settings (`crtl` + `comma`) 33 | * Search for "everywhere". 34 | * Check "Allow Setting Breakpoints in any file" 35 | * This helps the debugger work with Rust, even if optimizations have moved your code around. 36 | 37 | ![](/images/BreakpointsEverywhere.png) 38 | 39 | ### Enable Realtime Clippy 40 | * Open Settings (`ctrl` + `comma`) 41 | * Search for "cargo check" 42 | * Change "Rust Analyzer > Check Command" to "clippy" 43 | * By default, `cargo check` provides a very quick "did it compile?" check for your code. 44 | * Rust has a great linter called "Clippy" built in. 45 | * This helps reduce errors as you work, and often provides quick refactors into idiomatic Rust. 46 | 47 | ![](/images/RustAnalyzerClippy.png) 48 | -------------------------------------------------------------------------------- /day1/hour1/simple_login_test.md: -------------------------------------------------------------------------------- 1 | # Extending the Library 2 | 3 | > This will be live-coded. The GitHub version is [here](/src/auth_if/). You can try it in ReplIt here: [https://replit.com/@HerbertWolverso/SimpleLoginTest#login/src/main.rs](https://replit.com/@HerbertWolverso/SimpleLoginTest#login/src/main.rs) 4 | 5 | Just printing someone's name is a great start, but you probably want 6 | some user input. 7 | 8 | ## Initial Yes/No Authentication 9 | 10 | Let's go back to our `auth` project, and add a new function: 11 | 12 | ```rust 13 | pub fn is_login_allowed(name: &str) -> bool { 14 | name.to_lowercase().trim() == "herbert" 15 | } 16 | ``` 17 | 18 | There's a few things to note here: 19 | * We're applying `to_lowercase()` to ensure that even "HeRbErT" is in lower case. 20 | * We're calling `trim()` to remove any extraneous characters from the string. 21 | * We once-again use short-form return. True if the name is "herbert" 22 | 23 | Let's also add a couple of unit tests for this: 24 | 25 | ```rust 26 | #[test] 27 | fn test_case_and_trim() { 28 | assert!(is_login_allowed("HeRbErT")); 29 | assert!(is_login_allowed(" herbert\r\n")); 30 | } 31 | 32 | #[test] 33 | fn test_login_fail() { 34 | assert!(!is_login_allowed("bob")); 35 | } 36 | ``` 37 | 38 | The first test covers case and trimming. The second tests the *negative* of 39 | the function---it returns `false` for a username other than `herbert`. 40 | 41 | Test it with `cargo test`: 42 | 43 | ``` 44 | running 3 tests 45 | test tests::test_greet_user ... ok 46 | test tests::test_login_fail ... ok 47 | test tests::test_case_and_trim ... ok 48 | ``` 49 | 50 | ## Implement Yes/No Login 51 | 52 | Now return to your `login` program. Let's change it to require that the user provide their name, 53 | and we determine if they are allowed in or not. 54 | 55 | Change `main.rs` to read as follows: 56 | 57 | ```rust 58 | use auth_if::is_login_allowed; 59 | 60 | fn main() { 61 | println!("Welcome to the (Not Very) Secure Server"); 62 | println!("Enter your username:"); 63 | let mut input = String::new(); 64 | let stdin = std::io::stdin(); 65 | stdin.read_line(&mut input).unwrap(); 66 | if is_login_allowed(&input) { 67 | println!("Welcome, {input}"); 68 | } else { 69 | println!("Sorry, {input} is now allowed"); 70 | } 71 | } 72 | ``` 73 | 74 | What's new here? 75 | 76 | * `let mut input = String::new()` creates a new string. It is *mutable* you can change it after it has been created. Rust defaults to immutable variables. 77 | * Obtaining `stdin` gets a link the operating system's `stdin` system. 78 | * `read_line` reads a string. The mysterious `unwrap` function is there because `read_line` can fail. You might not have a keyboard attached, you might be running headless (without an input system at all). `unwrap` means "crash if an error occurs here". We're going to talk more and more about errors and results as we progress. 79 | * We're using an `if` statement to vary what happens for each possible code branch. Since `is_login_allowed` returns a `bool`, it can only be `true` or `false`. 80 | 81 | Let's run the program a couple of times (with `cargo run`): 82 | 83 | ``` 84 | Welcome to the (Not Very) Secure Server 85 | bob 86 | Sorry, bob 87 | is now allowed 88 | ``` 89 | 90 | Alternatively: 91 | 92 | ``` 93 | Welcome to the (Not Very) Secure Server 94 | Enter your username: 95 | herbert 96 | Welcome, herbert 97 | ``` 98 | 99 | Notice the strange line breaks? Reading from `stdin` includes `\r\n` (or just `\n` on non-Windows platforms) in your string. Rust dutifuly sends this to the output, causing a break. Just like you did in the login system, `trim()` can remove this. 100 | 101 | > If we're running faster than expected, connect the debugger to demonstrate this. -------------------------------------------------------------------------------- /day1/hour1/workspaces.md: -------------------------------------------------------------------------------- 1 | # Cargo Workspaces 2 | 3 | > This section is live-coded. You can use it online on ReplIt at [https://replit.com/@HerbertWolverso/HelloWorkspace](https://replit.com/@HerbertWolverso/HelloWorkspace). 4 | 5 | If you're working with more than one related project, `workspaces` can help you: 6 | 7 | * Workspaces combine all of your builds into a single `target` folder. No need to find every single `target/` folder when cleaning up. 8 | * Workspaces share downloads. If you have a bunch of projects with shared dependencies, workspaces build them once---and share the result. Faster compilation, less disk space usage. 9 | * A lot of cargo commands can be run in the "workspace root" with `--all` as a command-line flag, and will operate on all of them. Run all of your tests with `cargo test --all`, or build everything with `cargo build --all`. Beware: `cargo run --all` really will try and run every program you've created in this workspace. 10 | 11 | > While working on Hands-on Rust, I had so many examples outside of a workspace that I ran out of disk space and realized I was using hundreds of gigabytes with multiple copies of Legion and Bracket-Lib. Moving into a workspace meant I only had a single copy of each library, and was using a reasonable amount of disk space. 12 | 13 | ## Our Current Setup 14 | 15 | Go back to your source folder for today. In my case `c:\users\herbert\rust\live`. 16 | 17 | Open `Cargo.toml` and add a `workspace` section: 18 | 19 | ```toml 20 | [workspace] 21 | members = [] 22 | ``` 23 | 24 | The workspace automatically includes the top-level `src` directory. This is the *parent* 25 | workspace. 26 | 27 | It's often confusing if you open a workspace with several projects in it and `cargo run` just 28 | happens to run the first project you created! 29 | 30 | Edit `src/main.rs` to change the message printed: 31 | 32 | ```rust 33 | fn main() { 34 | println!("You probably wanted to run one of the nested workspaces."); 35 | } 36 | ``` 37 | 38 | Now, if someone accidentally runs the parent project they are notified of their mistake. 39 | 40 | Let's get back to where we were by creating a `login` project *inside* the workspace. 41 | 42 | ``` 43 | cd c:\users\herbert\rust\live 44 | cargo new login 45 | ``` 46 | 47 | You will see a warning: 48 | 49 | ``` 50 | warning: compiling this new package may not work due to invalid workspace configuration 51 | 52 | current package believes it's in a workspace when it's not: 53 | current: C:\Users\Herbert\Documents\Ardan\Rust Foundations 4 Day\src\hello_login_system\Cargo.toml 54 | workspace: C:\Users\Herbert\Documents\Ardan\Rust Foundations 4 Day\Cargo.toml 55 | error: failed to load manifest for workspace member `C:\Users\Herbert\Documents\Ardan\Rust Foundations 4 Day\hello_login_system` 56 | 57 | Caused by: 58 | failed to read `C:\Users\Herbert\Documents\Ardan\Rust Foundations 4 Day\hello_login_system\Cargo.toml` 59 | 60 | Caused by: 61 | The system cannot find the path specified. (os error 3) 62 | ``` 63 | 64 | > The paths will vary on your computer. In this case, it's referring to this Github repo. 65 | 66 | This is a really long-winded way of saying: *Don't forget to add the new project to your workspaces' members* 67 | 68 | Add the newly created project by re-opening the parent's `Cargo.toml` and adding it to your workspace: 69 | 70 | ```toml 71 | members = [ 72 | "login", # I find it helpful to include a note about what the project does, here. 73 | ] 74 | ``` 75 | 76 | ## Try It Out 77 | 78 | 1. Change directory to your workspace root. 79 | 2. Type `cargo run`. You'll see the message that you probably didn't mean to run this one. 80 | 3. Change directory to your `login` program. 81 | 4. Type `cargo run` and see "Hello World". 82 | 5. Notice that there's only one `target` folder for the whole workspace. ALL compilation artifacts go here. 83 | 84 | And that's it - you have a working workspace with all of its benefits. 85 | 86 | > The GitHub version uses one master workspace for *all* the examples. You can open [/Cargo.toml](/Cargo.toml) to see a really large workspace in action! 87 | -------------------------------------------------------------------------------- /day1/hour2/hour_2_wrap.md: -------------------------------------------------------------------------------- 1 | # Hour 2 Wrap 2 | 3 | In this hour, you've learned: 4 | 5 | * Create a basic Rust enumeration---a lot like other languages. 6 | * Use simple `match` statements on the enumeration. 7 | * Add data, included nested enumerations, to your enumeration branches. 8 | * Derive `PartialEq`, `Clone`, and `Debug` to save typing and add properties to your types. 9 | * Use complex `match` statements to handle complex data types. 10 | * Implement associated functions and member functions on enumerations. 11 | * Use *function pointers* to pass functions between functions. 12 | * Create `struct` types to bundle data together. 13 | * Implement functions on `struct` types. 14 | * Create an array of structures. 15 | * Return an array and *move* it. 16 | * Pass an array to a function as a *slice*, so the recipient doesn't need to know the container type. 17 | * Use *iterators* to process data types without resorting to loops. 18 | 19 | # TIME FOR A BREAK 20 | # COME BACK IN 12 MINUTES TO CONTINUE 21 | ## After the break, we're going to start to learn about vectors, hash maps, dependencies and serialization. -------------------------------------------------------------------------------- /day1/hour3/hashing.md: -------------------------------------------------------------------------------- 1 | # Hashing Passwords 2 | 3 | > This session is live-coded. The Github code is [here]() 4 | 5 | You probably don't want to be saving passwords in plain text. It's high on the list of "programming oopsies" that lead to security issues. 6 | 7 | Open `Cargo.toml` in your `authentication` project. We're going to add one more dependency: 8 | 9 | ```toml 10 | [package] 11 | name = "auth_passwords" 12 | version = "0.1.0" 13 | edition = "2021" 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | serde = { version = "1.0.152", features = [ "derive" ] } 19 | serde_json = "1.0.92" 20 | sha2 = "0" 21 | ``` 22 | 23 | Now open `lib.rs` and we'll create a function to hash passwords: 24 | 25 | ```rust 26 | pub fn hash_password(password: &str) -> String { 27 | use sha2::Digest; 28 | let mut hasher = sha2::Sha256::new(); 29 | hasher.update(password); 30 | format!("{:X}", hasher.finalize()) 31 | } 32 | ``` 33 | 34 | The `{:X}` in `format!` means "print in hexadecimal format". 35 | 36 | > Add salt! Normally, you wouldn't just use a direct hash - you'd manipulate `password` in some way. There are whole books and papers written on what makes for a good password salting solution. It's a bit out of scope for the class, so we'll keep it simple. 37 | 38 | ## Hashing New User Passwords 39 | 40 | Now lets update the `User` constructor (`new`) to automatically hash the passwords it is given: 41 | 42 | ```rust 43 | impl User { 44 | pub fn new(username: &str, password: &str, action: LoginAction) -> Self { 45 | Self { 46 | username: username.to_string(), 47 | password: hash_password(password), 48 | action 49 | } 50 | } 51 | } 52 | ``` 53 | 54 | Likewise, let's have the `login` function hash incoming passwords before comparison is performed: 55 | 56 | ```rust 57 | pub fn login(users: &HashMap, username: &str, password: &str) -> Option { 58 | let username = username.trim().to_lowercase(); 59 | let password = hash_password(password.trim()); 60 | 61 | users 62 | .get(&username) 63 | .filter(|user| user.password == password) 64 | .map(|user| user.action.clone()) 65 | } 66 | ``` 67 | 68 | ## Using More Secure Passwords 69 | 70 | Not switch over to `login`, and uncomment out `build_users_file()` again. Run the program, quit out and have a look at `users.json`: 71 | 72 | ```json 73 | { 74 | "bob": { 75 | "username": "bob", 76 | "password": "5E884898DA28047151D0E56F8DC6292773603D0D6AABBDD62A11EF721D1542D8", 77 | "action": { 78 | "Accept": "User" 79 | } 80 | }, 81 | "fred": { 82 | "username": "fred", 83 | "password": "5E884898DA28047151D0E56F8DC6292773603D0D6AABBDD62A11EF721D1542D8", 84 | "action": { 85 | "Denied": "PasswordExpired" 86 | } 87 | }, 88 | "herbert": { 89 | "username": "herbert", 90 | "password": "5E884898DA28047151D0E56F8DC6292773603D0D6AABBDD62A11EF721D1542D8", 91 | "action": { 92 | "Accept": "Admin" 93 | } 94 | } 95 | } 96 | ``` 97 | 98 | That's much harder to guess! Comment out `build_users_file()` once again, and test that you can still login. You can! -------------------------------------------------------------------------------- /day1/hour3/hour_3_wrap.md: -------------------------------------------------------------------------------- 1 | # Hour 3 Wrap 2 | 3 | In this hour, you've learned: 4 | 5 | * All about `Vec`, one of Rust's core containers. 6 | * Using `HashMap`, and when its a good replacement for `Vec`. 7 | * Serializing your data with `Serde` and `serde_json`. 8 | * Loading your data back again. 9 | * Securing passwords in serialized data with a hashing function. 10 | 11 | # TIME FOR A BREAK 12 | # COME BACK IN 12 MINUTES TO CONTINUE 13 | ## After the break, we're going to build a user management application. -------------------------------------------------------------------------------- /day1/readme.md: -------------------------------------------------------------------------------- 1 | # Day 1: Core Rust 2 | 3 | ## 1st Hour 4 | 5 | * [Class Introduction](./hour1/class_intro.md) 6 | * [Setup and Update Rust](./hour1/setup_rust.md) 7 | * [Configure your IDE](./hour1/setup_ide.md) 8 | * [Cargo Init and Hello World](./hour1/hello_world.md) 9 | * [Cargo Workspaces](./hour1/workspaces.md) 10 | * [Hello Library](./hour1/hello_library.md) 11 | * [Text Input & Better Unit Testing](./hour1/simple_login_test.md) 12 | * [Hour 1 Wrap](./hour1/hour_1_wrap.md) 13 | * **TIME FOR A BREAK** 14 | 15 | ## 2nd Hour 16 | 17 | * [Enumerations](./hour2/enums.md) 18 | * [Structures](./hour2/structs.md) 19 | * [Hour 2 Wrap](./hour2/hour_2_wrap.md) 20 | * **TIME FOR A BREAK** 21 | 22 | ## 3rd Hour 23 | 24 | * [Vectors](./hour3/vectors.md) 25 | * [HashMaps](./hour3/hashmaps.md) 26 | * [Serialization/Deserialization](./hour3/serialization.md) 27 | * [Hashing Passwords](./hour3/hashing.md) 28 | * [Hour 3 Wrap](./hour3/hour_3_wrap.md) 29 | * **TIME FOR A BREAK** 30 | 31 | ## 4th Hour 32 | 33 | * [Build a Command Line User Manager](./hour4/cli.md) 34 | * [Day Wrap](./day_1_wrap.md) 35 | * **SEE YOU TOMORROW** 36 | -------------------------------------------------------------------------------- /day2/hour1/day1_hour2_wrap.md: -------------------------------------------------------------------------------- 1 | # Day 2, Hour 1 Wrap 2 | 3 | We've covered: 4 | 5 | * Organizing your code into modules. 6 | * The 3 ways to make a module: 7 | * The `mod` block. 8 | * A single file, imported with `mod ;` 9 | * A directory containing a `mod.rs` file, imported with `mod ;`. 10 | * Handling Errors 11 | * Dealing with `Result` types. 12 | * Putting disparate error types in a `Box`. 13 | * Simplifying errors with `anyhow`. 14 | * Making better errors with `thiserror`. 15 | * Errors are NOT exceptions. 16 | 17 | # TIME FOR A BREAK 18 | # COME BACK IN 12 MINUTES TO CONTINUE 19 | ## After the break, we're going to start to learn about data types. -------------------------------------------------------------------------------- /day2/hour1/day2_intro.md: -------------------------------------------------------------------------------- 1 | # Day 2 2 | 3 | Yesterday, we learned a lot of the basics of Rust: 4 | * Workspaces, applications and libraries. 5 | * Importing dependencies. 6 | * Enumerations 7 | * Structures 8 | * Arrays 9 | * Vectors 10 | * HashMaps 11 | * Serialization 12 | * Hashing Passwords 13 | * A Full CLI application using `clap` 14 | 15 | Today, we're going to start putting our knowledge of the basic language to work---and being productive. We'll start by covering some of the sticking points people encounter when getting started with Rust: 16 | 17 | * Modules, Visibility and Organizing Your Code. 18 | * Error Handling 19 | * The Borrow Checker 20 | * Lifetimes 21 | * Resource Acquisition is Initialization 22 | 23 | Then, we're going to dive into Rust's promises of *Fearless Concurrency*. 24 | 25 | * Build a CPU-bound workload. 26 | * Take a look at a couple of other languages that have race condition problems. 27 | * Build the same program in Rust, and see how Rust saves our bacon. 28 | * Use *atomics* for thread-safe primitive variables. 29 | * Use `Rayon` to introduce really easy parallel programming for CPU-bound workloads. 30 | * Use `async` and `Tokio` to build a TCP networking server. 31 | 32 | At the end of today, you'll be able to write simple network services that use Rust to safely share data between processes, and divide your CPU-bound workloads into bite-sized chunks---using all the available CPU with limited effort. 33 | 34 | -------------------------------------------------------------------------------- /day2/hour1/documentation.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | > This will be a very quick section. It's live-coded, you can find the Github version [here](/src/docs/) and [here](/src/docs_exe) 4 | 5 | Building a library implies that you are sharing it--or intend to reuse it later. It's a great idea to include some documentation! I'm not going to make you sit and write documentation with me, that would be pretty dull. It is important that you know what's available, though. 6 | 7 | Open up `lib.rs`, and we'll start with a *library header*. At the top, add: 8 | 9 | ```rust 10 | //! # Authentication 11 | //! 12 | //! This module provides user validation and role assignment. 13 | ``` 14 | 15 | Now open up `main.rs` in `login`. Hover the mouse over `use authentication`, and you can see your module header: 16 | 17 | ![](/images/ModuleDocs.png) 18 | 19 | Ideally, you want to document everything that is publicly available. We won't do that today, we'd be here all morning. Let's tell Clippy to fire a warning for everything we *should* document. In `lib.rs`, add the following to the top: 20 | 21 | ```rust 22 | #![warn(missing_docs)] 23 | ``` 24 | 25 | * The `#!` means "do this for the whole crate, underneath this point. 26 | 27 | Notice that lots of warnings appeared! 28 | 29 | Let's document `hash_password`: 30 | 31 | ```rust 32 | /// `hash_password` applies a SHA256 digest hash to a string, and returns a 33 | /// string containing the hashed string, in hexadecimal format. 34 | /// 35 | /// ## Arguments 36 | /// 37 | /// * `password` - the password to hash. 38 | pub fn hash_password(password: &str) -> String { 39 | use sha2::Digest; 40 | let mut hasher = sha2::Sha256::new(); 41 | hasher.update(password); 42 | format!("{:X}", hasher.finalize()) 43 | } 44 | ``` 45 | 46 | Notice that we've used markdown. You can even use links if you want to. If you find a usage of `hash_password` and mouse over it, your documentation is now visible: 47 | 48 | ![](/images/FunctionDocs.png) 49 | 50 | ## Documentation Examples 51 | 52 | Let's make that documentation a little better by adding a usage example: 53 | 54 | ```rust 55 | /// ``` 56 | /// use docs::hash_password; 57 | /// println!("{}", hash_password("test")); 58 | /// ``` 59 | ``` 60 | 61 | Save the file, and run `cargo test`. The unit test executed your example! This is a great way to make sure that your docs stay synced with your code. 62 | 63 | We'll remove the warning directive so we don't see warnings as we continue learning. -------------------------------------------------------------------------------- /day2/hour2/intro.md: -------------------------------------------------------------------------------- 1 | # The Borrow Checker and Lifetimes 2 | 3 | Probably the most common thing that I hear from people migrating to Rust is: 4 | 5 | *I tried really hard to like it, but the borrow checker made it impossible to do anything.* 6 | 7 | So we're going to spend this hour going over some of the more common difficulties, and wrap up with some general advice on how to learn to love the borrow checker. It may be Stockholm Syndrome, but the borrow checker really does help. 8 | 9 | Later on, we'll see examples of the borrow checker prevents you from making terrible mistakes. 10 | 11 | > Google and Microsoft both report a 70% reduction in memory-related errors in their code after moving parts of their codebase to Rust. -------------------------------------------------------------------------------- /day2/hour2/raii.md: -------------------------------------------------------------------------------- 1 | # RAII: Resource Acquisition is Initialization 2 | 3 | We've used this a few times already, but I wanted to briefly mention RAII. It's one of the powerful features that helps make Rust a safer language. The feature originated in object-oriented languages such as SmallTalk and C++, and the idiom is important to "Modern" C++. In the OOP world, it's known as a "destructor". Unlike destructors in Java, you can be very sure it will run---but it *isn't* absolutely guaranteed to run if the program panics. 4 | 5 | Take the following Playground program: 6 | 7 | ```rust 8 | struct Droppable; 9 | 10 | impl Drop for Droppable { 11 | fn drop(&mut self) { 12 | println!("Destructing") 13 | } 14 | } 15 | 16 | fn main() { 17 | let x = Droppable; 18 | } 19 | ``` 20 | 21 | If you run it, you'll see "Destructing". Whenever anything that implements the "Drop" trait goes out of scope---or is destroyed with `std::mem::drop`, the `drop` function will run. 22 | 23 | This is remarkably handy. Rust uses it for file descriptors (closing the file when `File` leaves scope), locks (automatically relinquish a lock when the lock leaves scope). Network connections automatically close when they leave scope. 24 | 25 | You should use it, too. Whenever you acquire a finite resource, make sure you implement `Drop` to guarantee that you relinquish your hold on the program. 26 | 27 | Let's take a quick look at [/src/file_lock](/src/file_lock). 28 | 29 | ```rust 30 | use std::fs::remove_file; 31 | use std::time::Duration; 32 | use std::{path::Path, fs::File}; 33 | use std::io::Write; 34 | 35 | struct FileLock; 36 | 37 | impl FileLock { 38 | fn new() -> Self { 39 | let path = Path::new("file.lock"); 40 | if path.exists() { 41 | panic!("You can't run this program more than once"); 42 | } 43 | let mut output = File::create(path).unwrap(); 44 | write!(output, "locked").unwrap(); 45 | 46 | Self 47 | } 48 | } 49 | 50 | impl Drop for FileLock { 51 | fn drop(&mut self) { 52 | let path = Path::new("file.lock"); 53 | remove_file(path).unwrap(); 54 | } 55 | } 56 | 57 | fn main() { 58 | let _lock = FileLock::new(); 59 | // Pretend to do something important 60 | std::thread::sleep(Duration::from_secs(30)); 61 | } 62 | ``` 63 | 64 | When `FileLock` is created, it looks for a file named `file.lock`. If it exists, the program panics. If it doesn't, it makes the file. When `FileLock` is destroyed, the lock is deleted. 65 | 66 | Run the program: `file.lock` appears. 67 | Run in a second window: the program panics. 68 | 69 | Now hit `ctrl-c` to terminate. The file remains! We don't have time to dive into how to handle this yet, but [This Guide](https://rust-cli.github.io/book/in-depth/signals.html) will give you what you need to handle this eventuality. 70 | 71 | As we go into the break, think about how you can combine what you learned about reference counting with a `Drop` handler. You could bind a shared resource, lock access to it, and automatically disconnect when you are done. At very little performance cost. -------------------------------------------------------------------------------- /day2/hour3/count_primes.md: -------------------------------------------------------------------------------- 1 | # Counting Prime Numbers - Badly 2 | 3 | As we start to look at concurrency, we need a way to keep the CPU really busy. There are lots of good ways to determine if a number is prime. This isn't one of them: 4 | 5 | ```rust 6 | fn is_prime(n: u32) -> bool { 7 | (2 ..= n/2).all(|i| n % i != 0 ) 8 | } 9 | ``` 10 | 11 | > This is live-coded. The GitHub repo is [here](/src/count_primes/) 12 | 13 | Let's quickly unit test our function to make sure that it works. I grabbed a list of prime numbers from: [https://en.wikipedia.org/wiki/Prime_number](https://en.wikipedia.org/wiki/Prime_number) 14 | 15 | ```rust 16 | #[cfg(test)] 17 | mod test { 18 | use super::*; 19 | 20 | #[test] 21 | fn test_first_hundred_primes() { 22 | // List obtained from: https://en.wikipedia.org/wiki/Prime_number 23 | let primes: Vec = (2..100).filter(|n| is_prime(*n)).collect(); 24 | assert_eq!( 25 | primes, 26 | [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97] 27 | ); 28 | } 29 | } 30 | ``` 31 | 32 | And we'll do a very simple loop with a counter to count prime numbers: 33 | 34 | ```rust 35 | fn main() { 36 | let mut count = 0; 37 | let now = std::time::Instant::now(); 38 | for i in 2 .. MAX { 39 | if is_prime(i) { 40 | count += 1; 41 | } 42 | } 43 | let time = now.elapsed(); 44 | println!("Found {count} primes in {} seconds", time.as_secs_f32()); 45 | } 46 | ``` 47 | 48 | The result in release mode is: 49 | 50 | ``` 51 | Found 17984 primes in 1.0891765 seconds 52 | ``` 53 | 54 | The objective here is to heat the room up with our CPU. So we aren't too worried that it took 6 seconds in debug mode, and 1.08 seconds in release mode on my PC. 55 | 56 | We've built a really inefficient prime number finder. The reason for doing this is that we're going to dive into concurrency, and make this CPU-bound problem faster. 57 | -------------------------------------------------------------------------------- /day2/hour3/rayon.md: -------------------------------------------------------------------------------- 1 | # Using Rayon 2 | 3 | `Rayon` is a popular Rust crate that provides easy-to-use solutions for CPU bound problems. Rayon: 4 | 5 | * Uses a work-stealing thread pool for tasks. 6 | * Offers `par_iter` to readily transform regular iterator tasks into parallel tasks. 7 | * Is a lot like Intel's `Threaded Building Blocks` library, but with less pain. 8 | 9 | > This is live-coded. For the Github, see [here](/src/count_primes_rayon/) 10 | 11 | We'll start by making a new project and adding it to our workspace. 12 | 13 | ``` 14 | cargo init count_primes_rayon 15 | ``` 16 | 17 | (Edit `[members]`) 18 | 19 | Then we add Rayon to the dependencies: 20 | 21 | ``` 22 | cargo add rayon 23 | ``` 24 | 25 | Now, let's transform our prime counter into an iterator chain: 26 | 27 | ```rust 28 | fn is_prime(n: u32) -> bool { 29 | (2 ..= n/2).all(|i| n % i != 0 ) 30 | } 31 | 32 | fn main() { 33 | const MAX:u32 = 200000; 34 | let now = std::time::Instant::now(); 35 | 36 | let count = (2..MAX) 37 | .filter(|n| is_prime(*n)) 38 | .count(); 39 | 40 | let duration = now.elapsed(); 41 | println!("Found {count} primes in {} seconds", duration.as_secs_f32()); 42 | } 43 | ``` 44 | 45 | There's no parallelism here. Running it shows that it performs about equally to our single-threaded loop: 46 | 47 | ``` 48 | cargo run --release 49 | Found 17986 primes in 1.0798844 seconds 50 | ``` 51 | 52 | > This is live-coded. For the Github, see [here](/src/count_primes_rayon2/) 53 | 54 | Let's use Rayon. In the iterator chain, we need to add one line: 55 | 56 | ```rust 57 | let count = (2..MAX) 58 | .into_par_iter() 59 | .filter(|n| is_prime(*n)) 60 | .count(); 61 | ``` 62 | 63 | This will flag errors in the IDE. Let's use it to find the required imports. We end up with: 64 | 65 | ```rust 66 | use rayon::prelude::{IntoParallelIterator, ParallelIterator}; 67 | ``` 68 | 69 | Now when we run the program: 70 | 71 | ``` 72 | cargo run --release 73 | Found 17984 primes in 0.1061489 seconds 74 | ``` 75 | 76 | That was almost too easy. Rayon makes it very easy to build parallel versions of existing iterator calculations. 77 | 78 | ## Rayon Can Manage Your Threads 79 | 80 | > We're live coding, the Github example is [here](/src/rayon_threads/) 81 | 82 | Rayon can manage your threads, but because it's *task oriented* it works a little differently. This won't work: 83 | 84 | ```rust 85 | use std::{thread::sleep, time::Duration}; 86 | use rayon::spawn; 87 | 88 | fn hello(n: u64) { 89 | println!("Hello from thread {n}"); 90 | sleep(Duration::from_secs(n)); 91 | println!("Bye from thread {n}"); 92 | } 93 | 94 | fn main() { 95 | let mut threads = Vec::new(); 96 | for i in 0..8 { 97 | let my_i = i; 98 | threads.push(spawn(move || hello(my_i))); 99 | } 100 | for t in threads { 101 | t.join(); 102 | } 103 | } 104 | ``` 105 | 106 | Instead, Rayon combines jobs inside a `scope` - and a scope waits until all child jobs have finished: 107 | 108 | ```rust 109 | use std::{thread::sleep, time::Duration}; 110 | 111 | fn hello(n: u64) { 112 | println!("Hello from thread {n}"); 113 | sleep(Duration::from_secs(n)); 114 | println!("Bye from thread {n}"); 115 | } 116 | 117 | fn main() { 118 | rayon::scope(|s| { 119 | for i in 0..8 { 120 | let my_i = i; 121 | s.spawn(move |_| hello(my_i)); 122 | } 123 | }); 124 | 125 | } 126 | ``` 127 | 128 | Between these constructs, Rayon is often the best choice for taming CPU bound problems. Best of all: all of the data-race safety still works in Rayon. -------------------------------------------------------------------------------- /day2/hour3/shared.md: -------------------------------------------------------------------------------- 1 | # Shared Data 2 | 3 | You may want to keep the list of prime numbers, rather than just counting it. A naïve approach would be to have a shared list of prime numbers, and add to it. 4 | 5 | > We're live-coding. The Github repo is [here](/src/count_primes_shared/) 6 | 7 | We replace the counter with a Mutex-wrapped vector: 8 | 9 | ```rust 10 | static PRIMES: Mutex> = Mutex::new(Vec::new()); 11 | ``` 12 | 13 | The counting process changes to collecting a vector, and extending the shared vector (with locking): 14 | 15 | ```rust 16 | threads.push(std::thread::spawn(move || { 17 | let range = u32::max(2, counter*group) .. (i+1)*group; 18 | let my_primes: Vec = range.filter(|n| is_prime(*n)).collect(); 19 | PRIMES.lock().unwrap().extend(my_primes); 20 | })); 21 | ``` 22 | 23 | And summarizing becomes: 24 | 25 | ```rust 26 | println!("Found {} prime numbers in the range 2..{MAX}", PRIMES.lock().unwrap().len()); 27 | ``` 28 | 29 | Pretty straightforward: you've created a shared (static) vector, wrapped it with a Mutex and lock it/add to it when you have results. It's low on collisions, but if one thread has answers at the same time as another---the second one has to wait before it can insert data. 30 | 31 | ## Per-Thread Results 32 | 33 | > We're live-coding. The Github repo is [here](/src/count_primes_shared2/) 34 | 35 | Let's remove the `Mutex` requirement, and the shared `primes` vector altogether. Then we'll replace our list of threads with a more complex type: 36 | 37 | ```rust 38 | let mut threads: Vec>> = Vec::with_capacity(N_THREADS as usize); 39 | ``` 40 | 41 | We're telling Rust that the threads we are tracking will *return* a vector of `u32`. 42 | 43 | Now we make that true, by just returning the list of prime numbers instead of appending it to a shared structure: 44 | 45 | ```rust 46 | for i in 0 .. N_THREADS { 47 | let counter = i; 48 | threads.push(std::thread::spawn(move || { 49 | let range = u32::max(2, counter*group) .. (i+1)*group; 50 | range.filter(|n| is_prime(*n)).collect() 51 | })); 52 | } 53 | ``` 54 | 55 | When it comes time to wait for the threads, via the join handles---we retrieve each in turn. `join` returns a `Result` type, so we want to handle potential errors: 56 | 57 | ```rust 58 | let mut primes = Vec::new(); 59 | for thread in threads { 60 | if let Ok(new_primes) = thread.join() { 61 | primes.extend(new_primes); 62 | } else { 63 | println!("Something went wrong"); 64 | } 65 | } 66 | ``` 67 | 68 | Finally, we can just print the size of the vector with no locks: 69 | 70 | ```rust 71 | println!("Found {} prime numbers in the range 2..{MAX}", primes.len()); 72 | ``` 73 | 74 | Comparing the `Mutex` version (approx 0.283 seconds) with the lock-free version (approx 0.263 seconds)---lock free is faster when you have potential contention, **but its not that much faster**. `Mutex` is really, really fast. -------------------------------------------------------------------------------- /day2/readme.md: -------------------------------------------------------------------------------- 1 | ## Day 2 2 | 3 | ### Hour 1 4 | 5 | * [Day Intro](./hour1/day2_intro.md) 6 | * [Modules & Visibility](./hour1/modules.md) 7 | * [Documentation](./hour1/documentation.md) 8 | * [Error Handling](./hour1/errors.md) 9 | * [Hour wrap](./hour1/day1_hour2_wrap.md) 10 | * **TIME FOR A BREAK** 11 | 12 | ### Hour 2 13 | 14 | * [Introduction](./hour2/intro.md) 15 | * [The Borrow Checker](./hour2/borrow_checker.md) 16 | 17 | ### Hour 3 18 | 19 | * [Lifetimes](./hour2/lifetimes.md) 20 | * [Implementing OOP Patterns](./hour2/oop.md) 21 | 22 | ### Hour 4 23 | 24 | * [Resource Acquisition is Initialization](raii.md) 25 | * [Really Inefficiently Counting Prime Numbers](./hour3/count_primes.md) 26 | * [Data Race Time](./hour3/data_race.md) 27 | * [Atomically Counting Primes](./hour3/atomic.md) 28 | * [Counting Primes with Shared Data](./hour3/shared.md) 29 | * [Day Wrap] 30 | -------------------------------------------------------------------------------- /day3/hour2/constants.md: -------------------------------------------------------------------------------- 1 | # Constants 2 | 3 | This is one area that Modern C++ is ahead of Rust. C++ lets you do some amazing compile-time calculations, and is very flexible about making functions `constexpr`. The downside being that `constexpr` doesn't *guaranty* that a function is constant---it's more like a suggestion. 4 | 5 | ## Constant Variables 6 | 7 | You've used these already: 8 | 9 | ```rust 10 | const MY_CONST: usize = 12; 11 | ``` 12 | 13 | They are great, and work just like you'd expect. Set a value, and it works everywhere. 14 | 15 | You can do calculations in constants: 16 | 17 | ```rust 18 | const A: usize = 5; 19 | const B: usize = 6; 20 | const C: usize = A * B; 21 | ``` 22 | 23 | These are evaluated at compile time. If `A` and `B` are never used, other than as parts of the calculation---they will be eliminated from optimized builds. 24 | 25 | ## Compile-Dependent Constants 26 | 27 | You can use compile-time declarations to adjust constants depending upon compilation environment: 28 | 29 | ```rust 30 | #[cfg(debug)] 31 | const A: usize = 3; 32 | 33 | #[cfg(release)] 34 | const A: usize = 4; 35 | ``` 36 | 37 | A `debug` build will have a different value to a `release` build. You might gate by the type of operating system on which the program is compiled: 38 | 39 | ```rust 40 | #[cfg(target_family = "unix")] 41 | const A: usize = 3; 42 | #[cfg(not(target_family = "unix"))] 43 | const A: usize = 4; 44 | 45 | fn main() { 46 | println!("{A}"); 47 | } 48 | ``` 49 | 50 | (The Rust Playground is running a Unix!) 51 | 52 | ## Constant Functions 53 | 54 | You can add the `const` keyword to functions to have the function execute at compile time: 55 | 56 | ```rust 57 | const fn add(a: usize, b: usize) -> usize { 58 | a + b 59 | } 60 | 61 | const A: usize = add(4, 6); 62 | 63 | fn main() { 64 | println!("{A}"); 65 | } 66 | ``` 67 | 68 | You can even use the constant function with dynamic inputs: 69 | 70 | ```rust 71 | const fn add(a: usize, b: usize) -> usize { 72 | a + b 73 | } 74 | 75 | const A: usize = add(4, 6); 76 | 77 | fn main() { 78 | let mut i = 5; 79 | i += 3; 80 | println!("{}", add(A, i)); 81 | println!("{A}"); 82 | } 83 | ``` 84 | 85 | So let's try and make a more complicated constant function: 86 | 87 | ```rust 88 | const fn loopy() -> usize { 89 | let mut n = 1; 90 | for i in 0..20 { 91 | n += i; 92 | } 93 | n 94 | } 95 | 96 | const A: usize = loopy(); 97 | 98 | fn main() { 99 | println!("{A}"); 100 | } 101 | ``` 102 | 103 | We've added a *mutable* variable inside a constant. Surely that will fail? No! The error is that you aren't allowed to use `for` loops in constant functions. This is because the `Iterator` type is inherently not constant. 104 | 105 | Transforming to a `while` loop works fine: 106 | 107 | ```rust 108 | const fn loopy() -> usize { 109 | let mut n = 1; 110 | let mut i = 0; 111 | while i < 20 { 112 | n += i; 113 | i += 1; 114 | } 115 | n 116 | } 117 | ``` 118 | 119 | You can offload a fair amount of work this way: calculating constants up-front means one less calculation to do at run-time. 120 | 121 | ## What You Can't Do at Compile Time 122 | 123 | Unfortunately, there's a pretty big list of things you can't do at compile time --- yet. The Rust Core team are improving this. 124 | 125 | * You can't use floating point numbers, except as direct constants. `const n: f32 = 1.0` is fine. A function using floating point numbers won't work. 126 | * You can't use iterators. 127 | * You can't connect to external data sources - OTHER than files. The macros `include_str!` and `include_bytes!` can embed files in your binary. 128 | -------------------------------------------------------------------------------- /day3/hour2/generic_data.md: -------------------------------------------------------------------------------- 1 | # Generic Data 2 | 3 | You've seen how to implement generic functions. Let's take some time to implement our own generic data store. The objective here is to show that everything you've learned about generic functions also applies to data structures---and you will learn to use generics inside member functions. 4 | 5 | > This is live-coded. The GitHub version is [here](/src/generic_data/) 6 | 7 | We're going to make a sort-of useful data structure: a `StableVec` that retains ID numbers even after entries are removed. This allows you to reuse index numbers, at the expense of wasting memory. 8 | 9 | ## Creating a Struct with a Generic Type 10 | 11 | ```rust 12 | #[derive(Debug)] 13 | struct StableVec { 14 | data: Vec> 15 | } 16 | ``` 17 | 18 | You don't need to derive `Debug`, but it helps. You apply a `` to the type, and can use the `` as a placeholder inside the type. 19 | 20 | ## Creating a Constructor 21 | 22 | The `impl` is a bit different: 23 | 24 | ```rust 25 | impl StableVec { 26 | ``` 27 | 28 | You *declare* `T` after the implementation, and use it to fulfill the requirements of `StableVec`. Let's add some functions: 29 | 30 | ```rust 31 | impl StableVec { 32 | fn new() -> Self { 33 | Self { 34 | data: Vec::new(), 35 | } 36 | } 37 | 38 | fn push(&mut self, item: T) -> usize { 39 | let id = self.data.len(); 40 | self.data.push(Some(item)); 41 | id 42 | } 43 | 44 | fn remove(&mut self, id: usize) { 45 | self.data[id] = None; 46 | } 47 | 48 | fn get(&self, id: usize) -> &Option { 49 | &self.data[id] 50 | } 51 | } 52 | ``` 53 | 54 | We can implement `Index` to support access via `[3]`: 55 | 56 | ```rust 57 | impl Index for StableVec { 58 | type Output = Option; 59 | fn index(&self, index: usize) -> &Self::Output { 60 | &self.data[index] 61 | } 62 | } 63 | ``` 64 | 65 | And let's test it: 66 | 67 | ```rust 68 | fn main() { 69 | let mut store = StableVec::::new(); 70 | let a = store.push("A".to_string()); 71 | let b = store.push("B".to_string()); 72 | let c = store.push("C".to_string()); 73 | store.remove(b); 74 | println!("{:?}", store.get(a)); 75 | println!("{:?}", store.get(b)); 76 | println!("{:?}", store.get(c)); 77 | println!("{:?}", store[c]); 78 | } 79 | ``` -------------------------------------------------------------------------------- /day3/readme.md: -------------------------------------------------------------------------------- 1 | # Day 3 2 | 3 | ## Hour 1 4 | 5 | * [Easy Parallelism with Rayon](./hour3/rayon.md) 6 | * [Tokio and Async](./hour4/tokio.md) 7 | 8 | ## Hour 2 9 | 10 | * [TCP Networking with Tokio](../day2/hour4/tcp_server.md) 11 | * [Channels](../day2/hour4/channels.md) 12 | * [Global Variables](./hour1/globals.md) 13 | 14 | 15 | * [Synchronization Primitives](./hour1/sync.md) 16 | * [Wrapping Types: NewTypes](./hour1/new_types.md) 17 | * [Basic Traits](./hour1/traits.md) 18 | 19 | ## Hour 3 20 | 21 | * [Generic Data](./hour2/generic_data.md) 22 | * [Complex Generic Data](./hour2/generic_complex.md) 23 | 24 | ## Hour 4 25 | 26 | * [Constants & Constant Functions](./hour2/constants.md) 27 | * [Simple Macros](../day4//hour1/macros.md) 28 | * [Feature Flags](../day4/hour1/feature_flags.md) 29 | 30 | -------------------------------------------------------------------------------- /day4/hour1/benchmarks.md: -------------------------------------------------------------------------------- 1 | # Simple Benchmarks 2 | 3 | If you decide to take the "Debugging & Optimization" class, we'll delve into benchmarks in a lot of detail. 4 | 5 | ## Quick and Dirty 6 | 7 | A quick and dirty way to benchmark operations is to use the built in `Instant` and `Duration` types. For example: 8 | 9 | [Playground Link](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=52dedaf0c6963c7deb6a2728425b78c5) 10 | 11 | ```rust 12 | use std::time::Instant; 13 | 14 | fn main() { 15 | let now = Instant::now(); 16 | let mut i = 0; 17 | for j in 0 .. 1_000 { 18 | i += j*j; 19 | } 20 | let elapsed = now.elapsed(); 21 | println!("Time elapsed: {} nanos", elapsed.as_nanos()); 22 | println!("{i}"); 23 | } 24 | ``` 25 | 26 | This is handy when you just want to get a handle on how long something takes to run. It's not 100% accurate, because reading the clock isn't instantaneous. 27 | 28 | ## Embedding Benchmarks in your Tests 29 | 30 | There are some quite complicated test suites available, including `criterion`---which is excellent. We won't get to that in this class, but we will learn how to make use of unstable Rust features. 31 | 32 | Make sure that you have the `nightly` toolchain installed: 33 | 34 | ``` 35 | rustup install nightly 36 | ``` 37 | 38 | > Live-coded. See the Github link [here](/src/bench/) 39 | 40 | [Documentation for this unstable feature](https://doc.rust-lang.org/1.4.0/book/benchmark-tests.html) 41 | 42 | Now we'll write our test system, using the basic `Cargo` generated empty library as a base: 43 | 44 | ``` 45 | cargo init bench --lib 46 | ``` 47 | 48 | ```rust 49 | #![feature(test)] 50 | 51 | extern crate test; 52 | 53 | pub fn add(left: usize, right: usize) -> usize { 54 | left + right 55 | } 56 | 57 | #[cfg(test)] 58 | mod tests { 59 | use super::*; 60 | use test::Bencher; 61 | 62 | #[test] 63 | fn it_works() { 64 | let result = add(2, 2); 65 | assert_eq!(result, 4); 66 | } 67 | 68 | #[bench] 69 | fn bench_add(b: &mut Bencher) { 70 | b.iter(|| add(2, 4)); 71 | } 72 | } 73 | ``` 74 | 75 | You can run your benchmark with: 76 | 77 | ``` 78 | cargo +nightly bench 79 | ``` 80 | 81 | This does carry a pretty big downside over using a full benchmark suite such as `Criterion`: you just required that your build use Rust's `nightly` channel. 82 | 83 | The easiest thing to do is to comment out the benchmark when you aren't using it. -------------------------------------------------------------------------------- /day4/hour1/benchmarks2.md: -------------------------------------------------------------------------------- 1 | # More Complicated Benchmarks 2 | 3 | Recommended crate: 4 | 5 | * [Criterion](https://github.com/bheisler/criterion.rs) 6 | 7 | ## Setting Up Criterion 8 | 9 | In `Cargo.toml`, add: 10 | 11 | ```toml 12 | [dev-dependencies] 13 | criterion = { version = "0.4", features = [ "html_reports" ] } 14 | 15 | [[bench]] 16 | name = "my_benchmark" 17 | harness = false 18 | ``` 19 | 20 | > `[dev-dependencies]` is new! This is a dependency that is *only* loaded by development tools, and isn't integrated into your final program. No space is wasted. 21 | 22 | Create `benchmark2/benches/my_benchmark.rs`: 23 | 24 | ```rust 25 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 26 | 27 | fn fibonacci(n: u64) -> u64 { 28 | match n { 29 | 0 => 1, 30 | 1 => 1, 31 | n => fibonacci(n-1) + fibonacci(n-2), 32 | } 33 | } 34 | 35 | fn criterion_benchmark(c: &mut Criterion) { 36 | c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20)))); 37 | } 38 | 39 | criterion_group!(benches, criterion_benchmark); 40 | criterion_main!(benches); 41 | ``` 42 | 43 | > Taken from the Criterion demo page. The "Optimizing & Debugging Rust" class goes into a lot more detail. 44 | 45 | Run `cargo bench` and see the result. 46 | 47 | Go to `target/criterion` and you have a full HTML report with statistics. -------------------------------------------------------------------------------- /day4/hour1/dashmap.md: -------------------------------------------------------------------------------- 1 | # Avoiding Locks with DashMap 2 | 3 | Sometimes, locks really bog you down. Not in speed: in the complexity of your application. You have updates coming in from multiple sources, readers obtaining information, and the locking soup is annoying. 4 | 5 | Good news: there are "lock free" structures available in Rust. They typically either emulate a generational memory scheme (similar to Java), or use atomics *internally* and quickly replace old data with new. 6 | 7 | ## Add Dashmap 8 | 9 | Add Dashmap to your project with `cargo add dashmap`. Note that we renamed the `dashmap` project because you can't have a dependency with the same name as your project. 10 | 11 | You also need `once_cell` again, `cargo add once_cell`. 12 | 13 | > We're livecoding. See [the Github repo](/src/dashmap/) for details. 14 | 15 | ```rust 16 | use dashmap::DashMap; 17 | use once_cell::sync::Lazy; 18 | use std::{thread, time::Duration}; 19 | 20 | static MAP: Lazy> = Lazy::new(DashMap::new); 21 | 22 | fn main() { 23 | let mut threads = Vec::new(); 24 | 25 | // Adder Threads 26 | for i in 0..10 { 27 | threads.push(thread::spawn(move || { 28 | for _ in 0..100 { 29 | if let Some(mut count) = MAP.get_mut(&i) { 30 | *count += 1; 31 | } else { 32 | MAP.insert(i, 1); 33 | } 34 | std::thread::sleep(Duration::from_secs_f32(0.1)); 35 | } 36 | })); 37 | } 38 | 39 | // Reader Threads 40 | for i in 0..10 { 41 | threads.push(thread::spawn(move || { 42 | for _ in 0..20 { 43 | if let Some(count) = MAP.get(&i) { 44 | println!("Count of {i}: {}", *count); 45 | std::thread::sleep(Duration::from_secs_f32(0.5)); 46 | } 47 | } 48 | })); 49 | } 50 | 51 | for t in threads { 52 | let _ = t.join(); 53 | } 54 | } 55 | ``` 56 | 57 | The output shows that despite there not being any locks, we're ticking upwards. 58 | 59 | **Going lock-free is not free. Lock-less structures are a little slower.** -------------------------------------------------------------------------------- /day4/hour1/feature_flags.md: -------------------------------------------------------------------------------- 1 | # Feature Flags 2 | 3 | Sometimes, you want library consumers to be able to change the behavior of your library. You've used this when you specified `features = [ "derive" ]` with Serde and other crates. 4 | 5 | > This is live-coded. The Github version uses [this library]() and [this client]() 6 | 7 | Create two new applications: 8 | 9 | ``` 10 | cargo new feature_lib --lib 11 | cargo new feature_exe 12 | ``` 13 | 14 | Let's start with `flags_lib/Cargo.toml`: 15 | 16 | ```toml 17 | [package] 18 | name = "flags_lib" 19 | version = "0.1.0" 20 | edition = "2021" 21 | 22 | [features] 23 | default = [ "normal" ] 24 | normal = [] 25 | other = [] 26 | 27 | [dependencies] 28 | ``` 29 | 30 | We've created a `normal` and `other` feature. The `[]` indicates that they don't turn on other features. `default` indicates that if you don't specify defaults, `normal` will be active. 31 | 32 | Now open `flags_lib/lib.rs`: 33 | 34 | ```rust 35 | pub const MODE: &str = "NORMAL"; 36 | pub const MODE: &str = "OTHER"; 37 | ``` 38 | 39 | Defining a constant and redefining it doesn't work. But you can see what we're trying to do. Let's require features to enable this constant: 40 | 41 | ```rust 42 | #[cfg(feature = "normal")] 43 | pub const MODE: &str = "NORMAL"; 44 | 45 | #[cfg(feature = "other")] 46 | pub const MODE: &str = "OTHER"; 47 | ``` 48 | 49 | Now if feature is "normal", `MODE` will equal `NORMAL`. If "other" is enabled, mode will equal `OTHER`. 50 | 51 | Open up `flags_exe\Cargo.toml`: 52 | 53 | ```toml 54 | [package] 55 | name = "flags_exe" 56 | version = "0.1.0" 57 | edition = "2021" 58 | 59 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 60 | 61 | [dependencies] 62 | flags_lib = { path = "../flags_lib" } 63 | ``` 64 | 65 | And the `main.rs` file: 66 | 67 | ```rust 68 | use flags_lib::MODE; 69 | 70 | fn main() { 71 | println!("{MODE}"); 72 | } 73 | ``` 74 | 75 | If we `cargo run`, we see: 76 | 77 | ``` 78 | NORMAL 79 | ``` 80 | 81 | Now edit `Cargo.toml` again: 82 | 83 | ```toml 84 | flags_lib = { path = "../flags_lib", default_features = false, features = [ "other" ] } 85 | ``` 86 | 87 | Running the program shows `OTHER`. 88 | 89 | ## Feature Flag Conditionals 90 | 91 | You'll also notice that VSCode has lit up `lib.rs` in red. You can't define a constant twice, and it's possible to leave `default_features` enabled and *also* enable `other`. 92 | 93 | So we add conditions to our feature flags: 94 | 95 | ```rust 96 | #[cfg(all(not(feature = "other"), feature = "normal"))] 97 | pub const MODE: &str = "NORMAL"; 98 | 99 | #[cfg(all(not(feature = "normal"), feature = "other"))] 100 | pub const MODE: &str = "OTHER"; 101 | ``` 102 | 103 | Unfortunately, you need to start using external crates to make this cleaner. -------------------------------------------------------------------------------- /day4/hour1/nostd.md: -------------------------------------------------------------------------------- 1 | # Life Without the Standard Library 2 | 3 | We're not going to dive into this in-depth, but you should be aware that just occasionally you need to work without the standard library. You might be working on a Linux kernel module, a tiny embedded system or similar. 4 | 5 | You can make very small binaries, and interface with your platforms libraries---but you've lost a lot of the collections and magic that make using Rust easy. 6 | 7 | This is an advanced topic, I mostly wanted you to know that it exists. Rust is a true systems language - you can build an operating system with it if you want! -------------------------------------------------------------------------------- /day4/hour1/parking_lot.md: -------------------------------------------------------------------------------- 1 | # Parking Lot 2 | 3 | I'm mostly bringing this one up to help with repetitive strain injury. 4 | 5 | Whenever you've used `Mutex` or `RwLock`, you've had to handle errors from the lock. It's unlikely that these errors are recoverable, since the most common errors are a dead-lock! 6 | 7 | If you include the `parking_lot` crate, it includes versions of `Mutex` and `RwLock` that don't require the `unwrap`. They otherwise function very similarly. On some platforms (Windows), `parking_lot` is a touch faster than the standard library. 8 | 9 | -------------------------------------------------------------------------------- /day4/hour1/safety.md: -------------------------------------------------------------------------------- 1 | # Rust Safety Guarantees 2 | 3 | Just to finish off, I'd like to remind you what is and isn't made safe by Rust. 4 | 5 | ## Memory Safety 6 | 7 | Rust's memory safety guarantees specifically ensure that you won't have use-after free, buffer overrun, dangling pointer, or iterator invalidation. These account for a LOT of security vulnerabilities. 8 | 9 | Rust specifically does NOT guarantee that you won't experience a memory leak. It even includes a call (`std::mem::forget`) to *explicitly* leak memory! With that said, Rust does make it tough to leak memory---the defaults are very safe, and drop-handling/RAII does a great job of largely removing the problem. 10 | 11 | ## Thread Safety 12 | 13 | Rust prevents data-races, a very common form of error in multi-threaded systems. 14 | 15 | Rust does *not* prevent you from creating a deadlock by misusing locks (such as Mutex). The RAII scope-bound locks make it easier to avoid this pitfall, but you still have to be careful. 16 | 17 | ## Type Safety 18 | 19 | Rust does a great job of enforcing type-safety, but you have to elect to use it (with `NewTypes` and similar). 20 | 21 | ## The "Unsafe" Tag 22 | 23 | Marking a block of code `unsafe` does *not* mean that it will explode. It means that it cannot be proved to Rust's static analyzer as safe. You'll always need `unsafe` when you talk directly to hardware, call programs through FFI (since *they* can't offer Rust guarantees). 24 | 25 | ## Audits 26 | 27 | If you use Rust in production, do yourself a favor: run `cargo install cargo-audit` and periodically run `cargo audit`. (Github can do this for you). It will find any CVE reports for crates that you are using. 28 | -------------------------------------------------------------------------------- /day4/readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Day 4 3 | 4 | ## Hour 1 5 | 6 | * [`parking_lot` for easier locks](./hour1/parking_lot.md) 7 | * [Simple Benchmarks](./hour1/benchmarks.md) 8 | * [Complex Benchmarks](./hour1/benchmarks2.md) 9 | 10 | 11 | ## Hour 2 12 | 13 | * [Putting it together: a login server](./hour1/tcp_login.md) 14 | * [A Web Application](./hour1/rocket.md) 15 | 16 | ## Hour 3 17 | 18 | * [How fast are our network services?](./hour1/netbench.md) 19 | 20 | ## Hour 4 21 | 22 | * [Life Without the Standard Library](./hour1/nostd.md) 23 | * [Safety Guarantees](./hour1/safety.md) -------------------------------------------------------------------------------- /images/.~lock.Timing Estimate.ods#: -------------------------------------------------------------------------------- 1 | ,DESKTOP-HL4FQ7M/Herbert,DESKTOP-HL4FQ7M,13.02.2023 12:28,file:///C:/Users/Herbert/AppData/Roaming/LibreOffice/4; -------------------------------------------------------------------------------- /images/BreakpointsEverywhere.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/images/BreakpointsEverywhere.png -------------------------------------------------------------------------------- /images/ColoredUsers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/images/ColoredUsers.png -------------------------------------------------------------------------------- /images/ExpandMacro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/images/ExpandMacro.png -------------------------------------------------------------------------------- /images/FunctionDocs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/images/FunctionDocs.png -------------------------------------------------------------------------------- /images/Hands-on Rust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/images/Hands-on Rust.png -------------------------------------------------------------------------------- /images/ModuleDocs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/images/ModuleDocs.png -------------------------------------------------------------------------------- /images/ModuleErrors2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/images/ModuleErrors2.png -------------------------------------------------------------------------------- /images/ModuleErrorsVsCode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/images/ModuleErrorsVsCode.png -------------------------------------------------------------------------------- /images/PuttyEcho.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/images/PuttyEcho.png -------------------------------------------------------------------------------- /images/Rust Brain Teasers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/images/Rust Brain Teasers.png -------------------------------------------------------------------------------- /images/RustAnalyzerClippy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/images/RustAnalyzerClippy.png -------------------------------------------------------------------------------- /images/RustUp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/images/RustUp.png -------------------------------------------------------------------------------- /images/Timing Estimate.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/images/Timing Estimate.ods -------------------------------------------------------------------------------- /images/VectorGrowth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/images/VectorGrowth.png -------------------------------------------------------------------------------- /images/ardanlabs-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/images/ardanlabs-logo.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Ultimate Rust 1: Foundations 2 | 3 | ![](/images/ardanlabs-logo.png) 4 | 5 | Presented by [Ardan Labs](https://www.ardanlabs.com/), Ultima Rust: Foundations gives you a "zero to hero" class to get you started with Rust. You'll learn the basics of Rust, before diving into hands-on learning that can help you build a Rust foundation into your architecture. Take advantage of the speed and safety of Rust, tame the borrow and lifetime checkers, and pick up some tricks to help you become productive in Rust. 6 | 7 | This is a four day class. Each day is 4 hours, with a short break at the end of each hour. 8 | 9 | Classes are presented in a live-coding workshop environment. You are encouraged to code along, and keep this guide as a road map into Rust. 10 | 11 | ## Pre-Requisites 12 | 13 | You need: 14 | 15 | * Rust installed (via RustUp.rs) 16 | * An IDE configured to use Rust Analyzer. 17 | * The instructor will use Visual Studio Code, so if you want to match the examples exactly with what you see, this is the recommended platform. It is [available free](https://code.visualstudio.com/download) for Mac, Windows and Linux. 18 | 19 | You should also clone a copy of these notes for yourself: 20 | 21 | ``` 22 | git clone https://github.com/thebracket/ArdanUltimateRustFoundations.git 23 | ``` 24 | 25 | ## Class Overview 26 | 27 | **[Day 1](/day1/)** 28 | 29 | * [Introduction](./day1/hour1/class_intro.md#class-overview) 30 | * [Setup & Update Rust](./day1/hour1/setup_rust.md) 31 | * [Visual Studio Code](./day1/hour1/setup_ide.md) 32 | * [cargo init and "Hello World"](./day1/hour1/hello_world.md) 33 | * [Cargo Workspaces](./day1/hour1/workspaces.md) 34 | * [Hello Library](./day1/hour1/hello_library.md) 35 | * [Text Input & Better Unit Testing](./day1/hour1/simple_login_test.md) 36 | * [Enumerations/Unions](./day1/hour2/enums.md) 37 | * [Structures](./day1/hour2/structs.md) 38 | * [Arrays & Iterators](./day1/hour2/structs.md) 39 | * [Vectors](./day1/hour3/vectors.md) 40 | * [HashMaps](./day1/hour3/hashmaps.md) 41 | * [Serialization](./day1/hour3/serialization.md) 42 | * [Hashing Passwords](./day1/hour3/hashing.md) 43 | * [A CLI Application](./day1/hour4/cli.md) 44 | 45 | **[Day 2](/day2/)** 46 | 47 | * [Modules & Visibility](./day2/hour1/modules.md) 48 | * [Documentation](./day2/hour1/documentation.md) 49 | * [Error Handling](./day2/hour1/errors.md) 50 | * [The Borrow Checker](./day2/hour2/borrow_checker.md) 51 | * [Lifetimes](./day2/hour2/lifetimes.md) 52 | * [OOP Patterns](/day2/hour2/oop.md) 53 | * [RAII - Drop Cleanup](./day2/hour2/raii.md) 54 | * [CPU Bound Workload](./day2/hour3/count_primes.md) 55 | * [Racing Data](./day2/hour3/data_race.md) 56 | * [Atomically Counting Primes](./day2/hour3/atomic.md) 57 | * [Shared Data](./day2/hour3/shared.md) 58 | 59 | **[Day 3](/day3/)** 60 | 61 | * [Easy Concurrency with Rayon](./day2/hour3/rayon.md) 62 | * [Async with Tokio](./day2/hour4/tokio.md) 63 | * [TCP Server with Tokio](./day2/hour4/tcp_server.md) 64 | * [Sending Receiving data - Channels](./day2/hour4/channels.md) 65 | * [Global Variables](./day3/hour1/globals.md) 66 | * [Synchronization Primitives](./day3/hour1/sync.md) 67 | * [Type Safety with NewTypes](./day3/hour1/new_types.md) 68 | * [Basic Traits](./day3/hour1/traits.md) 69 | * [Generic Data Types](./day3/hour2/generic_data.md) 70 | * [More Complex Generic Data](/day3/hour2/generic_complex.md) 71 | * [Constants & Constant Functions](./day3/hour2/constants.md) 72 | * [Macros](./day4/hour1/macros.md) 73 | * [Feature Flags](./day4/hour1/feature_flags.md) 74 | 75 | **[Day 4](/day4/)** 76 | 77 | * [Simpler Locks: Parking Lot](./day4/hour1/parking_lot.md) 78 | * [Avoiding Locks with DashMap](./day4/hour1/dashmap.md) 79 | * [Simple Benchmarks](./day4/hour1/benchmarks.md) 80 | * [Complex Benchmarks - A Quick Visit](./day4/hour1/benchmarks2.md) 81 | * [Putting it together: a TCP login server](./day4/hour1/tcp_login.md) 82 | * [A Simple Web Application](./day4/hour1/rocket.md) 83 | * [How fast are our network services?](./day4/hour1/netbench.md) 84 | * [Life without the Standard Library](./day4/hour1/nostd.md) 85 | * [Rust's Safety Guarantees](./day4/hour1/safety.md) 86 | * Wrap-Up 87 | -------------------------------------------------------------------------------- /src/auth_enum/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_enum" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/auth_enum/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn greet_user(name: &str) -> String { 2 | format!("Hello {name}") 3 | } 4 | 5 | pub fn is_login_allowed(name: &str) -> bool { 6 | name.to_lowercase().trim() == "herbert" 7 | } 8 | 9 | #[derive(PartialEq, Debug)] 10 | pub enum LoginAction { 11 | Admin, 12 | User, 13 | Denied, 14 | } 15 | 16 | pub fn login(name: &str) -> LoginAction { 17 | match name.to_lowercase().trim() { 18 | "herbert" => LoginAction::Admin, 19 | "bob" | "fred" => LoginAction::User, 20 | _ => LoginAction::Denied, 21 | } 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use super::*; 27 | 28 | #[test] 29 | fn test_greet_user() { 30 | assert_eq!("Hello Herbert", greet_user("Herbert")); 31 | } 32 | 33 | #[test] 34 | fn test_case_and_trim() { 35 | assert!(is_login_allowed("HeRbErT")); 36 | assert!(is_login_allowed(" herbert\r\n")); 37 | } 38 | 39 | #[test] 40 | fn test_login_fail() { 41 | assert!(!is_login_allowed("bob")); 42 | } 43 | 44 | #[test] 45 | fn test_enums() { 46 | assert_eq!(login("Herbert"), LoginAction::Admin); 47 | assert_eq!(login("bob"), LoginAction::User); 48 | assert_eq!(login("fred"), LoginAction::User); 49 | assert_eq!(login("anonymous"), LoginAction::Denied); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/auth_enum2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_enum2" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/auth_enum2/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn greet_user(name: &str) -> String { 2 | format!("Hello {name}") 3 | } 4 | 5 | pub fn is_login_allowed(name: &str) -> bool { 6 | name.to_lowercase().trim() == "herbert" 7 | } 8 | 9 | #[derive(PartialEq, Debug)] 10 | pub enum Role { 11 | Admin, 12 | User, 13 | Limited 14 | } 15 | 16 | #[derive(PartialEq, Debug)] 17 | pub enum DeniedReason { 18 | PasswordExpired, 19 | AccountLocked{reason: String}, 20 | } 21 | 22 | #[derive(PartialEq, Debug)] 23 | pub enum LoginAction { 24 | Accept(Role), 25 | Denied(DeniedReason), 26 | } 27 | 28 | pub fn login(name: &str) -> LoginAction { 29 | match name.to_lowercase().trim() { 30 | "herbert" => LoginAction::Accept(Role::Admin), 31 | "bob" => LoginAction::Accept(Role::User), 32 | "fred" => LoginAction::Denied(DeniedReason::PasswordExpired), 33 | _ => LoginAction::Denied(DeniedReason::AccountLocked { reason: "Not on the list".to_string() }) 34 | } 35 | } 36 | 37 | #[cfg(test)] 38 | mod tests { 39 | use super::*; 40 | 41 | #[test] 42 | fn test_greet_user() { 43 | assert_eq!("Hello Herbert", greet_user("Herbert")); 44 | } 45 | 46 | #[test] 47 | fn test_case_and_trim() { 48 | assert!(is_login_allowed("HeRbErT")); 49 | assert!(is_login_allowed(" herbert\r\n")); 50 | } 51 | 52 | #[test] 53 | fn test_login_fail() { 54 | assert!(!is_login_allowed("bob")); 55 | } 56 | 57 | #[test] 58 | fn test_enums() { 59 | assert_eq!(login("Herbert"), LoginAction::Accept(Role::Admin)); 60 | assert_eq!(login("bob"), LoginAction::Accept(Role::User)); 61 | assert_eq!(login("fred"), LoginAction::Denied(DeniedReason::PasswordExpired)); 62 | assert_eq!(login("anonymous"), LoginAction::Denied(DeniedReason::AccountLocked { reason: "Not on the list".to_string() })); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/auth_enum3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_enum3" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/auth_enum3/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn greet_user(name: &str) -> String { 2 | format!("Hello {name}") 3 | } 4 | 5 | pub fn is_login_allowed(name: &str) -> bool { 6 | name.to_lowercase().trim() == "herbert" 7 | } 8 | 9 | #[derive(PartialEq, Debug)] 10 | pub enum Role { 11 | Admin, 12 | User, 13 | Limited 14 | } 15 | 16 | #[derive(PartialEq, Debug)] 17 | pub enum DeniedReason { 18 | PasswordExpired, 19 | AccountLocked{reason: String}, 20 | } 21 | 22 | #[derive(PartialEq, Debug)] 23 | pub enum LoginAction { 24 | Accept(Role), 25 | Denied(DeniedReason), 26 | } 27 | 28 | pub fn login(name: &str) -> Option { 29 | match name.to_lowercase().trim() { 30 | "herbert" => Some(LoginAction::Accept(Role::Admin)), 31 | "bob" => Some(LoginAction::Accept(Role::User)), 32 | "fred" => Some(LoginAction::Denied(DeniedReason::PasswordExpired)), 33 | "kevin" => Some(LoginAction::Denied(DeniedReason::AccountLocked { reason: "Call Human Resources!".to_string() })), 34 | _ => None, 35 | } 36 | } 37 | 38 | #[cfg(test)] 39 | mod tests { 40 | use super::*; 41 | 42 | #[test] 43 | fn test_greet_user() { 44 | assert_eq!("Hello Herbert", greet_user("Herbert")); 45 | } 46 | 47 | #[test] 48 | fn test_case_and_trim() { 49 | assert!(is_login_allowed("HeRbErT")); 50 | assert!(is_login_allowed(" herbert\r\n")); 51 | } 52 | 53 | #[test] 54 | fn test_login_fail() { 55 | assert!(!is_login_allowed("bob")); 56 | } 57 | 58 | #[test] 59 | fn test_enums() { 60 | assert_eq!(login("Herbert"), Some(LoginAction::Accept(Role::Admin))); 61 | assert_eq!(login("bob"), Some(LoginAction::Accept(Role::User))); 62 | assert_eq!(login("fred"), Some(LoginAction::Denied(DeniedReason::PasswordExpired))); 63 | assert_eq!(login("anonymous"), None); 64 | if let Some(LoginAction::Denied(DeniedReason::AccountLocked { reason: _ })) = login("kevin") { 65 | // All is well 66 | } else { 67 | panic!("Failed to read kevin"); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/auth_enum_exe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_enum_exe" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | auth_enum = { path = "../auth_enum" } 10 | -------------------------------------------------------------------------------- /src/auth_enum_exe/src/main.rs: -------------------------------------------------------------------------------- 1 | use auth_enum::{login, LoginAction}; 2 | 3 | fn main() { 4 | println!("Welcome to the (Not Very) Secure Server"); 5 | println!("Enter your username:"); 6 | let mut input = String::new(); 7 | let stdin = std::io::stdin(); 8 | stdin.read_line(&mut input).unwrap(); 9 | 10 | match login(&input) { 11 | LoginAction::Admin => println!("You are administrator"), 12 | LoginAction::User => println!("You have regular user rights"), 13 | LoginAction::Denied => println!("You are not allowed in"), 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/auth_enum_exe3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_enum_exe3" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | auth_enum3 = { path = "../auth_enum3" } -------------------------------------------------------------------------------- /src/auth_enum_exe3/src/main.rs: -------------------------------------------------------------------------------- 1 | use auth_enum3::{login, LoginAction, Role, DeniedReason}; 2 | 3 | fn main() { 4 | println!("Welcome to the (Not Very) Secure Server"); 5 | println!("Enter your username:"); 6 | let mut input = String::new(); 7 | let stdin = std::io::stdin(); 8 | stdin.read_line(&mut input).unwrap(); 9 | 10 | match login(&input) { 11 | None => { 12 | println!("{} is not a known user.", input.trim()); 13 | println!("This is where we handle new users."); 14 | } 15 | Some(LoginAction::Accept(role)) => { 16 | println!("Welcome: {}", input.trim()); 17 | match role { 18 | Role::Admin => println!("With great power, comes great responsibility."), 19 | Role::Limited => println!("You have read-only access."), 20 | Role::User => println!("You have regular user access"), 21 | } 22 | } 23 | Some(LoginAction::Denied(DeniedReason::PasswordExpired)) => { 24 | println!("Unfortunately, your password has expired."); 25 | println!("It needs to 800 characters long and contain an entire sonnet."); 26 | } 27 | Some(LoginAction::Denied(DeniedReason::AccountLocked { reason })) => { 28 | println!("Sorry, {}, your login was denied.", input.trim()); 29 | println!("Reason: {reason}"); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/auth_enum_fn/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_enum_fn" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/auth_enum_fn/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn greet_user(name: &str) -> String { 2 | format!("Hello {name}") 3 | } 4 | 5 | pub fn is_login_allowed(name: &str) -> bool { 6 | name.to_lowercase().trim() == "herbert" 7 | } 8 | 9 | #[derive(PartialEq, Debug)] 10 | pub enum Role { 11 | Admin, 12 | User, 13 | Limited 14 | } 15 | 16 | #[derive(PartialEq, Debug)] 17 | pub enum DeniedReason { 18 | PasswordExpired, 19 | AccountLocked{reason: String}, 20 | } 21 | 22 | #[derive(PartialEq, Debug)] 23 | pub enum LoginAction { 24 | Accept(Role), 25 | Denied(DeniedReason), 26 | } 27 | 28 | impl LoginAction { 29 | fn standard_user() -> Option { 30 | Some(LoginAction::Accept(Role::User)) 31 | } 32 | 33 | pub fn do_login(&self, on_success: fn(&Role), on_denied: fn(&DeniedReason)) { 34 | match self { 35 | Self::Accept(role) => on_success(role), 36 | Self::Denied(reason) => on_denied(reason), 37 | } 38 | } 39 | } 40 | 41 | pub fn login(name: &str) -> Option { 42 | match name.to_lowercase().trim() { 43 | "herbert" => Some(LoginAction::Accept(Role::Admin)), 44 | "bob" => LoginAction::standard_user(), 45 | "fred" => Some(LoginAction::Denied(DeniedReason::PasswordExpired)), 46 | "kevin" => Some(LoginAction::Denied(DeniedReason::AccountLocked { reason: "Call Human Resources!".to_string() })), 47 | _ => None, 48 | } 49 | } 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::*; 54 | 55 | #[test] 56 | fn test_greet_user() { 57 | assert_eq!("Hello Herbert", greet_user("Herbert")); 58 | } 59 | 60 | #[test] 61 | fn test_case_and_trim() { 62 | assert!(is_login_allowed("HeRbErT")); 63 | assert!(is_login_allowed(" herbert\r\n")); 64 | } 65 | 66 | #[test] 67 | fn test_login_fail() { 68 | assert!(!is_login_allowed("bob")); 69 | } 70 | 71 | #[test] 72 | fn test_enums() { 73 | assert_eq!(login("Herbert"), Some(LoginAction::Accept(Role::Admin))); 74 | assert_eq!(login("bob"), Some(LoginAction::Accept(Role::User))); 75 | assert_eq!(login("fred"), Some(LoginAction::Denied(DeniedReason::PasswordExpired))); 76 | assert_eq!(login("anonymous"), None); 77 | if let Some(LoginAction::Denied(DeniedReason::AccountLocked { reason: _ })) = login("kevin") { 78 | // All is well 79 | } else { 80 | panic!("Failed to read kevin"); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/auth_enum_fn_exe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_enum_fn_exe" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | auth_enum_fn = { path = "../auth_enum_fn" } 10 | -------------------------------------------------------------------------------- /src/auth_enum_fn_exe/src/main.rs: -------------------------------------------------------------------------------- 1 | use auth_enum_fn::*; 2 | 3 | fn user_accepted(role: &Role) { 4 | println!("You are logged in as a {role:?}"); 5 | } 6 | 7 | fn main() { 8 | println!("Welcome to the (Not Very) Secure Server"); 9 | println!("Enter your username:"); 10 | let mut input = String::new(); 11 | let stdin = std::io::stdin(); 12 | stdin.read_line(&mut input).unwrap(); 13 | 14 | match login(&input) { 15 | None => { 16 | println!("{} is not a known user.", input.trim()); 17 | println!("This is where we handle new users."); 18 | } 19 | Some(login_action) => { 20 | login_action.do_login( 21 | user_accepted, 22 | |reason| { 23 | println!("Access denied"); 24 | println!("{reason:?}"); 25 | } 26 | ) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/auth_hashmap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_hashmap" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/auth_hashmap/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[derive(Clone, Debug)] 4 | pub struct User { 5 | pub username: String, 6 | pub password: String, 7 | pub action: LoginAction, 8 | } 9 | 10 | impl User { 11 | pub fn new(username: &str, password: &str, action: LoginAction) -> Self { 12 | Self { 13 | username: username.to_string(), 14 | password: password.to_string(), 15 | action 16 | } 17 | } 18 | } 19 | 20 | pub fn get_users() -> HashMap { 21 | /*let mut result = HashMap::new(); 22 | result.insert("herbert".to_string(), User::new("herbert", "password", LoginAction::Accept(Role::Admin))); 23 | result*/ 24 | let mut users = vec![ 25 | User::new("herbert", "password", LoginAction::Accept(Role::Admin)), 26 | User::new("bob", "password", LoginAction::Accept(Role::User)), 27 | User::new("fred", "password", LoginAction::Denied(DeniedReason::PasswordExpired)), 28 | ]; 29 | /*users 30 | .iter() // Create an iterator 31 | .map(|user| (user.username.clone(), user.clone()) ) 32 | .collect()*/ 33 | users 34 | .drain(0..) 35 | .map(|user| ( user.username.clone(), user )) 36 | .collect() 37 | } 38 | 39 | pub fn login(users: &HashMap, username: &str, password: &str) -> Option { 40 | let username = username.trim().to_lowercase(); 41 | let password = password.trim(); 42 | 43 | users 44 | .get(&username) 45 | .filter(|user| user.password == password) 46 | .map(|user| user.action.clone()) 47 | } 48 | 49 | #[derive(PartialEq, Debug, Clone)] 50 | pub enum Role { 51 | Admin, 52 | User, 53 | Limited 54 | } 55 | 56 | #[derive(PartialEq, Debug, Clone)] 57 | pub enum DeniedReason { 58 | PasswordExpired, 59 | AccountLocked{reason: String}, 60 | } 61 | 62 | #[derive(PartialEq, Debug, Clone)] 63 | pub enum LoginAction { 64 | Accept(Role), 65 | Denied(DeniedReason), 66 | } 67 | 68 | impl LoginAction { 69 | pub fn do_login(&self, on_success: fn(&Role), on_denied: fn(&DeniedReason)) { 70 | match self { 71 | Self::Accept(role) => on_success(role), 72 | Self::Denied(reason) => on_denied(reason), 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/auth_hashmap_exe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_hashmap_exe" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | auth_hashmap = { path = "../auth_hashmap" } -------------------------------------------------------------------------------- /src/auth_hashmap_exe/src/main.rs: -------------------------------------------------------------------------------- 1 | use auth_hashmap::*; 2 | 3 | fn user_accepted(role: &Role) { 4 | println!("You are logged in as a {role:?}"); 5 | } 6 | 7 | fn main() { 8 | let users = get_users(); 9 | 10 | println!("Welcome to the (Not Very) Secure Server"); 11 | println!("Enter your username:"); 12 | let mut username = String::new(); 13 | let mut password = String::new(); 14 | let stdin = std::io::stdin(); 15 | stdin.read_line(&mut username).unwrap(); 16 | println!("Enter your password:"); 17 | stdin.read_line(&mut password).unwrap(); 18 | 19 | match login(&users, &username, &password) { 20 | None => { 21 | println!("{} is not a known user.", username.trim()); 22 | println!("This is where we handle new users."); 23 | } 24 | Some(login_action) => { 25 | login_action.do_login( 26 | user_accepted, 27 | |reason| { 28 | println!("Access denied"); 29 | println!("{reason:?}"); 30 | } 31 | ) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/auth_if/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_if" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/auth_if/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn greet_user(name: &str) -> String { 2 | format!("Hello {name}") 3 | } 4 | 5 | pub fn is_login_allowed(name: &str) -> bool { 6 | name.to_lowercase().trim() == "herbert" 7 | } 8 | 9 | #[cfg(test)] 10 | mod tests { 11 | use super::*; 12 | 13 | #[test] 14 | fn test_greet_user() { 15 | assert_eq!("Hello Herbert", greet_user("Herbert")); 16 | } 17 | 18 | #[test] 19 | fn test_case_and_trim() { 20 | assert!(is_login_allowed("HeRbErT")); 21 | assert!(is_login_allowed(" herbert\r\n")); 22 | } 23 | 24 | #[test] 25 | fn test_login_fail() { 26 | assert!(!is_login_allowed("bob")); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/auth_if_exe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_if_exe" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | auth_if = { path = "../auth_if" } -------------------------------------------------------------------------------- /src/auth_if_exe/src/main.rs: -------------------------------------------------------------------------------- 1 | use auth_if::is_login_allowed; 2 | 3 | fn main() { 4 | println!("Welcome to the (Not Very) Secure Server"); 5 | println!("Enter your username:"); 6 | let mut input = String::new(); 7 | let stdin = std::io::stdin(); 8 | stdin.read_line(&mut input).unwrap(); 9 | if is_login_allowed(&input) { 10 | println!("Welcome, {input}"); 11 | } else { 12 | println!("Sorry, {input} is now allowed"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/auth_json/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_json" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | serde = { version = "1.0.152", features = [ "derive" ] } 10 | serde_json = "1.0.92" 11 | -------------------------------------------------------------------------------- /src/auth_json/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Debug, Serialize, Deserialize)] 5 | pub struct User { 6 | pub username: String, 7 | pub password: String, 8 | pub action: LoginAction, 9 | } 10 | 11 | impl User { 12 | pub fn new(username: &str, password: &str, action: LoginAction) -> Self { 13 | Self { 14 | username: username.to_string(), 15 | password: password.to_string(), 16 | action 17 | } 18 | } 19 | } 20 | 21 | pub fn build_users_file() { 22 | use std::io::Write; 23 | 24 | let users = get_users(); 25 | //let json = serde_json::to_string(&users).unwrap(); 26 | let json = serde_json::to_string_pretty(&users).unwrap(); 27 | let mut f = std::fs::File::create("users.json").unwrap(); 28 | f.write_all(json.as_bytes()).unwrap(); 29 | } 30 | 31 | #[allow(dead_code)] 32 | fn get_users_old() -> HashMap { 33 | /*let mut result = HashMap::new(); 34 | result.insert("herbert".to_string(), User::new("herbert", "password", LoginAction::Accept(Role::Admin))); 35 | result*/ 36 | let mut users = vec![ 37 | User::new("herbert", "password", LoginAction::Accept(Role::Admin)), 38 | User::new("bob", "password", LoginAction::Accept(Role::User)), 39 | User::new("fred", "password", LoginAction::Denied(DeniedReason::PasswordExpired)), 40 | ]; 41 | /*users 42 | .iter() // Create an iterator 43 | .map(|user| (user.username.clone(), user.clone()) ) 44 | .collect()*/ 45 | users 46 | .drain(0..) 47 | .map(|user| ( user.username.clone(), user )) 48 | .collect() 49 | } 50 | 51 | pub fn get_users() -> HashMap { 52 | let json = std::fs::read_to_string("users.json").unwrap(); 53 | serde_json::from_str(&json).unwrap() 54 | } 55 | 56 | pub fn login(users: &HashMap, username: &str, password: &str) -> Option { 57 | let username = username.trim().to_lowercase(); 58 | let password = password.trim(); 59 | 60 | users 61 | .get(&username) 62 | .filter(|user| user.password == password) 63 | .map(|user| user.action.clone()) 64 | } 65 | 66 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 67 | pub enum Role { 68 | Admin, 69 | User, 70 | Limited 71 | } 72 | 73 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 74 | pub enum DeniedReason { 75 | PasswordExpired, 76 | AccountLocked{reason: String}, 77 | } 78 | 79 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 80 | pub enum LoginAction { 81 | Accept(Role), 82 | Denied(DeniedReason), 83 | } 84 | 85 | impl LoginAction { 86 | pub fn do_login(&self, on_success: fn(&Role), on_denied: fn(&DeniedReason)) { 87 | match self { 88 | Self::Accept(role) => on_success(role), 89 | Self::Denied(reason) => on_denied(reason), 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/auth_json_exe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_json_exe" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | auth_json = { path = "../auth_json" } -------------------------------------------------------------------------------- /src/auth_json_exe/src/main.rs: -------------------------------------------------------------------------------- 1 | use auth_json::*; 2 | 3 | fn user_accepted(role: &Role) { 4 | println!("You are logged in as a {role:?}"); 5 | } 6 | 7 | fn main() { 8 | //build_users_file(); 9 | let users = get_users(); 10 | 11 | println!("Welcome to the (Not Very) Secure Server"); 12 | println!("Enter your username:"); 13 | let mut username = String::new(); 14 | let mut password = String::new(); 15 | let stdin = std::io::stdin(); 16 | stdin.read_line(&mut username).unwrap(); 17 | println!("Enter your password:"); 18 | stdin.read_line(&mut password).unwrap(); 19 | 20 | match login(&users, &username, &password) { 21 | None => { 22 | println!("{} is not a known user.", username.trim()); 23 | println!("This is where we handle new users."); 24 | } 25 | Some(login_action) => { 26 | login_action.do_login( 27 | user_accepted, 28 | |reason| { 29 | println!("Access denied"); 30 | println!("{reason:?}"); 31 | } 32 | ) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/auth_json_exe/users.json: -------------------------------------------------------------------------------- 1 | { 2 | "bob": { 3 | "username": "bob", 4 | "password": "password", 5 | "action": { 6 | "Accept": "User" 7 | } 8 | }, 9 | "herbert": { 10 | "username": "herbert", 11 | "password": "password", 12 | "action": { 13 | "Accept": "Admin" 14 | } 15 | }, 16 | "toby": { 17 | "username": "toby", 18 | "password": "password", 19 | "action": { 20 | "Denied": "PasswordExpired" 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/auth_passwords/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_passwords" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | serde = { version = "1.0.152", features = [ "derive" ] } 10 | serde_json = "1.0.92" 11 | sha2 = "0" 12 | -------------------------------------------------------------------------------- /src/auth_passwords/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Debug, Serialize, Deserialize)] 5 | pub struct User { 6 | pub username: String, 7 | pub password: String, 8 | pub action: LoginAction, 9 | } 10 | 11 | impl User { 12 | pub fn new(username: &str, password: &str, action: LoginAction) -> Self { 13 | Self { 14 | username: username.to_string(), 15 | password: hash_password(password), 16 | action 17 | } 18 | } 19 | } 20 | 21 | pub fn hash_password(password: &str) -> String { 22 | use sha2::Digest; 23 | let mut hasher = sha2::Sha256::new(); 24 | hasher.update(password); 25 | format!("{:X}", hasher.finalize()) 26 | } 27 | 28 | pub fn build_users_file() { 29 | use std::io::Write; 30 | 31 | let users = get_users_old(); 32 | //let json = serde_json::to_string(&users).unwrap(); 33 | let json = serde_json::to_string_pretty(&users).unwrap(); 34 | let mut f = std::fs::File::create("users.json").unwrap(); 35 | f.write_all(json.as_bytes()).unwrap(); 36 | } 37 | 38 | #[allow(dead_code)] 39 | fn get_users_old() -> HashMap { 40 | /*let mut result = HashMap::new(); 41 | result.insert("herbert".to_string(), User::new("herbert", "password", LoginAction::Accept(Role::Admin))); 42 | result*/ 43 | let mut users = vec![ 44 | User::new("herbert", "password", LoginAction::Accept(Role::Admin)), 45 | User::new("bob", "password", LoginAction::Accept(Role::User)), 46 | User::new("fred", "password", LoginAction::Denied(DeniedReason::PasswordExpired)), 47 | ]; 48 | /*users 49 | .iter() // Create an iterator 50 | .map(|user| (user.username.clone(), user.clone()) ) 51 | .collect()*/ 52 | users 53 | .drain(0..) 54 | .map(|user| ( user.username.clone(), user )) 55 | .collect() 56 | } 57 | 58 | pub fn get_users() -> HashMap { 59 | let json = std::fs::read_to_string("users.json").unwrap(); 60 | serde_json::from_str(&json).unwrap() 61 | } 62 | 63 | pub fn login(users: &HashMap, username: &str, password: &str) -> Option { 64 | let username = username.trim().to_lowercase(); 65 | let password = hash_password(password.trim()); 66 | 67 | users 68 | .get(&username) 69 | .filter(|user| user.password == password) 70 | .map(|user| user.action.clone()) 71 | } 72 | 73 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 74 | pub enum Role { 75 | Admin, 76 | User, 77 | Limited 78 | } 79 | 80 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 81 | pub enum DeniedReason { 82 | PasswordExpired, 83 | AccountLocked{reason: String}, 84 | } 85 | 86 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 87 | pub enum LoginAction { 88 | Accept(Role), 89 | Denied(DeniedReason), 90 | } 91 | 92 | impl LoginAction { 93 | pub fn do_login(&self, on_success: fn(&Role), on_denied: fn(&DeniedReason)) { 94 | match self { 95 | Self::Accept(role) => on_success(role), 96 | Self::Denied(reason) => on_denied(reason), 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/auth_passwords_exe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_passwords_exe" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | auth_passwords = { path = "../auth_passwords" } -------------------------------------------------------------------------------- /src/auth_passwords_exe/src/main.rs: -------------------------------------------------------------------------------- 1 | use auth_passwords::*; 2 | 3 | fn user_accepted(role: &Role) { 4 | println!("You are logged in as a {role:?}"); 5 | } 6 | 7 | fn main() { 8 | //build_users_file(); 9 | let users = get_users(); 10 | 11 | println!("Welcome to the (Not Very) Secure Server"); 12 | println!("Enter your username:"); 13 | let mut username = String::new(); 14 | let mut password = String::new(); 15 | let stdin = std::io::stdin(); 16 | stdin.read_line(&mut username).unwrap(); 17 | println!("Enter your password:"); 18 | stdin.read_line(&mut password).unwrap(); 19 | 20 | match login(&users, &username, &password) { 21 | None => { 22 | println!("{} is not a known user.", username.trim()); 23 | println!("This is where we handle new users."); 24 | } 25 | Some(login_action) => { 26 | login_action.do_login( 27 | user_accepted, 28 | |reason| { 29 | println!("Access denied"); 30 | println!("{reason:?}"); 31 | } 32 | ) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/auth_passwords_exe/users.json: -------------------------------------------------------------------------------- 1 | { 2 | "bob": { 3 | "username": "bob", 4 | "password": "5E884898DA28047151D0E56F8DC6292773603D0D6AABBDD62A11EF721D1542D8", 5 | "action": { 6 | "Accept": "User" 7 | } 8 | }, 9 | "fred": { 10 | "username": "fred", 11 | "password": "5E884898DA28047151D0E56F8DC6292773603D0D6AABBDD62A11EF721D1542D8", 12 | "action": { 13 | "Denied": "PasswordExpired" 14 | } 15 | }, 16 | "herbert": { 17 | "username": "herbert", 18 | "password": "5E884898DA28047151D0E56F8DC6292773603D0D6AABBDD62A11EF721D1542D8", 19 | "action": { 20 | "Accept": "Admin" 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/auth_struct/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_struct" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/auth_struct/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub struct User { 2 | pub username: String, 3 | pub password: String, 4 | pub action: LoginAction, 5 | } 6 | 7 | impl User { 8 | pub fn new(username: &str, password: &str, action: LoginAction) -> Self { 9 | Self { 10 | username: username.to_string(), 11 | password: password.to_string(), 12 | action 13 | } 14 | } 15 | } 16 | 17 | pub fn get_users() -> [User; 3] { 18 | [ 19 | User::new("herbert", "password", LoginAction::Accept(Role::Admin)), 20 | User::new("bob", "password", LoginAction::Accept(Role::User)), 21 | User::new("fred", "password", LoginAction::Denied(DeniedReason::PasswordExpired)), 22 | ] 23 | } 24 | 25 | pub fn login(users: &[User], username: &str, password: &str) -> Option { 26 | let username = username.trim().to_lowercase(); 27 | let password = password.trim(); 28 | users 29 | .iter() 30 | .find(|u| u.username == username && u.password == password).map(|user| user.action.clone()) 31 | } 32 | 33 | #[derive(PartialEq, Debug, Clone)] 34 | pub enum Role { 35 | Admin, 36 | User, 37 | Limited 38 | } 39 | 40 | #[derive(PartialEq, Debug, Clone)] 41 | pub enum DeniedReason { 42 | PasswordExpired, 43 | AccountLocked{reason: String}, 44 | } 45 | 46 | #[derive(PartialEq, Debug, Clone)] 47 | pub enum LoginAction { 48 | Accept(Role), 49 | Denied(DeniedReason), 50 | } 51 | 52 | impl LoginAction { 53 | pub fn do_login(&self, on_success: fn(&Role), on_denied: fn(&DeniedReason)) { 54 | match self { 55 | Self::Accept(role) => on_success(role), 56 | Self::Denied(reason) => on_denied(reason), 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/auth_struct_exe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_struct_exe" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | auth_struct = { path = "../auth_struct" } -------------------------------------------------------------------------------- /src/auth_struct_exe/src/main.rs: -------------------------------------------------------------------------------- 1 | use auth_struct::*; 2 | 3 | fn user_accepted(role: &Role) { 4 | println!("You are logged in as a {role:?}"); 5 | } 6 | 7 | fn main() { 8 | let users = get_users(); 9 | 10 | println!("Welcome to the (Not Very) Secure Server"); 11 | println!("Enter your username:"); 12 | let mut username = String::new(); 13 | let mut password = String::new(); 14 | let stdin = std::io::stdin(); 15 | stdin.read_line(&mut username).unwrap(); 16 | println!("Enter your password:"); 17 | stdin.read_line(&mut password).unwrap(); 18 | 19 | match login(&users, &username, &password) { 20 | None => { 21 | println!("{} is not a known user.", username.trim()); 22 | println!("This is where we handle new users."); 23 | } 24 | Some(login_action) => { 25 | login_action.do_login( 26 | user_accepted, 27 | |reason| { 28 | println!("Access denied"); 29 | println!("{reason:?}"); 30 | } 31 | ) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/auth_userman/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_userman" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | serde = { version = "1.0.152", features = [ "derive" ] } 10 | serde_json = "1.0.92" 11 | sha2 = "0" 12 | -------------------------------------------------------------------------------- /src/auth_userman/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Debug, Serialize, Deserialize)] 5 | pub struct User { 6 | pub username: String, 7 | pub password: String, 8 | pub action: LoginAction, 9 | } 10 | 11 | impl User { 12 | pub fn new(username: &str, password: &str, action: LoginAction) -> Self { 13 | Self { 14 | username: username.to_string(), 15 | password: hash_password(password), 16 | action 17 | } 18 | } 19 | } 20 | 21 | pub fn hash_password(password: &str) -> String { 22 | use sha2::Digest; 23 | let mut hasher = sha2::Sha256::new(); 24 | hasher.update(password); 25 | format!("{:X}", hasher.finalize()) 26 | } 27 | 28 | pub fn build_users_file() { 29 | use std::io::Write; 30 | 31 | let users = get_users_old(); 32 | //let json = serde_json::to_string(&users).unwrap(); 33 | let json = serde_json::to_string_pretty(&users).unwrap(); 34 | let mut f = std::fs::File::create("users.json").unwrap(); 35 | f.write_all(json.as_bytes()).unwrap(); 36 | } 37 | 38 | pub fn save_users_file(users: &HashMap) { 39 | use std::io::Write; 40 | let json = serde_json::to_string_pretty(&users).unwrap(); 41 | let mut f = std::fs::File::create("users.json").unwrap(); 42 | f.write_all(json.as_bytes()).unwrap(); 43 | } 44 | 45 | #[allow(dead_code)] 46 | fn get_users_old() -> HashMap { 47 | /*let mut result = HashMap::new(); 48 | result.insert("herbert".to_string(), User::new("herbert", "password", LoginAction::Accept(Role::Admin))); 49 | result*/ 50 | let mut users = vec![ 51 | User::new("herbert", "password", LoginAction::Accept(Role::Admin)), 52 | User::new("bob", "password", LoginAction::Accept(Role::User)), 53 | User::new("fred", "password", LoginAction::Denied(DeniedReason::PasswordExpired)), 54 | ]; 55 | /*users 56 | .iter() // Create an iterator 57 | .map(|user| (user.username.clone(), user.clone()) ) 58 | .collect()*/ 59 | users 60 | .drain(0..) 61 | .map(|user| ( user.username.clone(), user )) 62 | .collect() 63 | } 64 | 65 | pub fn get_users() -> HashMap { 66 | let json = std::fs::read_to_string("users.json").unwrap(); 67 | serde_json::from_str(&json).unwrap() 68 | } 69 | 70 | pub fn login(users: &HashMap, username: &str, password: &str) -> Option { 71 | let username = username.trim().to_lowercase(); 72 | let password = hash_password(password.trim()); 73 | 74 | users 75 | .get(&username) 76 | .filter(|user| user.password == password) 77 | .map(|user| user.action.clone()) 78 | } 79 | 80 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 81 | pub enum Role { 82 | Admin, 83 | User, 84 | Limited 85 | } 86 | 87 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 88 | pub enum DeniedReason { 89 | PasswordExpired, 90 | AccountLocked{reason: String}, 91 | } 92 | 93 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 94 | pub enum LoginAction { 95 | Accept(Role), 96 | Denied(DeniedReason), 97 | } 98 | 99 | impl LoginAction { 100 | pub fn do_login(&self, on_success: fn(&Role), on_denied: fn(&DeniedReason)) { 101 | match self { 102 | Self::Accept(role) => on_success(role), 103 | Self::Denied(reason) => on_denied(reason), 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/auth_vec/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_vec" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/auth_vec/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub struct User { 2 | pub username: String, 3 | pub password: String, 4 | pub action: LoginAction, 5 | } 6 | 7 | impl User { 8 | pub fn new(username: &str, password: &str, action: LoginAction) -> Self { 9 | Self { 10 | username: username.to_string(), 11 | password: password.to_string(), 12 | action 13 | } 14 | } 15 | } 16 | 17 | pub fn get_users() -> Vec { 18 | vec![ 19 | User::new("herbert", "password", LoginAction::Accept(Role::Admin)), 20 | User::new("bob", "password", LoginAction::Accept(Role::User)), 21 | User::new("fred", "password", LoginAction::Denied(DeniedReason::PasswordExpired)), 22 | ] 23 | } 24 | 25 | pub fn login(users: &[User], username: &str, password: &str) -> Option { 26 | let username = username.trim().to_lowercase(); 27 | let password = password.trim(); 28 | users 29 | .iter() 30 | .find(|u| u.username == username && u.password == password).map(|user| user.action.clone()) 31 | } 32 | 33 | #[derive(PartialEq, Debug, Clone)] 34 | pub enum Role { 35 | Admin, 36 | User, 37 | Limited 38 | } 39 | 40 | #[derive(PartialEq, Debug, Clone)] 41 | pub enum DeniedReason { 42 | PasswordExpired, 43 | AccountLocked{reason: String}, 44 | } 45 | 46 | #[derive(PartialEq, Debug, Clone)] 47 | pub enum LoginAction { 48 | Accept(Role), 49 | Denied(DeniedReason), 50 | } 51 | 52 | impl LoginAction { 53 | pub fn do_login(&self, on_success: fn(&Role), on_denied: fn(&DeniedReason)) { 54 | match self { 55 | Self::Accept(role) => on_success(role), 56 | Self::Denied(reason) => on_denied(reason), 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/auth_vec_exe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth_vec_exe" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | auth_vec = { path = "../auth_vec" } -------------------------------------------------------------------------------- /src/auth_vec_exe/src/main.rs: -------------------------------------------------------------------------------- 1 | use auth_vec::*; 2 | 3 | fn user_accepted(role: &Role) { 4 | println!("You are logged in as a {role:?}"); 5 | } 6 | 7 | fn main() { 8 | let mut users = get_users(); 9 | users.push(User::new("kent", "password", LoginAction::Accept(Role::Limited))); 10 | users.remove(0); 11 | users.retain(|u| u.username == "kent"); 12 | 13 | let usernames: Vec<&String> = users 14 | .iter() 15 | .map(|u| &u.username) 16 | .collect(); 17 | println!("{usernames:#?}"); 18 | 19 | println!("Welcome to the (Not Very) Secure Server"); 20 | println!("Enter your username:"); 21 | let mut username = String::new(); 22 | let mut password = String::new(); 23 | let stdin = std::io::stdin(); 24 | stdin.read_line(&mut username).unwrap(); 25 | println!("Enter your password:"); 26 | stdin.read_line(&mut password).unwrap(); 27 | 28 | match login(&users, &username, &password) { 29 | None => { 30 | println!("{} is not a known user.", username.trim()); 31 | println!("This is where we handle new users."); 32 | } 33 | Some(login_action) => { 34 | login_action.do_login( 35 | user_accepted, 36 | |reason| { 37 | println!("Access denied"); 38 | println!("{reason:?}"); 39 | } 40 | ) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/bench/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bench" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/bench/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | extern crate test; 3 | 4 | pub fn add(left: usize, right: usize) -> usize { 5 | left + right 6 | } 7 | 8 | #[cfg(test)] 9 | mod tests { 10 | use super::*; 11 | use test::Bencher; 12 | 13 | #[test] 14 | fn it_works() { 15 | let result = add(2, 2); 16 | assert_eq!(result, 4); 17 | } 18 | 19 | #[bench] 20 | fn bench_add(b: &mut Bencher) { 21 | b.iter(|| add(2, 4)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/borrow_iter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "borrow_iter" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/borrow_iter/src/main.rs: -------------------------------------------------------------------------------- 1 | struct Node { 2 | parent: usize, 3 | } 4 | 5 | fn main() { 6 | let mut nodes = vec![ 7 | Node{parent: 0}, 8 | Node{parent: 1}, 9 | Node{parent: 2}, 10 | ]; 11 | 12 | /*for i in 1..nodes.len() { 13 | if nodes[i].parent == 1 { 14 | nodes[i-1].parent = 0; 15 | } 16 | }*/ 17 | 18 | nodes.iter().enumerate().for_each(|(i, node)| { 19 | if node.parent == 1 && i > 0 { 20 | nodes[i-1].parent = 0; 21 | } 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /src/borrow_move1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "borrow_move1" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/borrow_move1/src/main.rs: -------------------------------------------------------------------------------- 1 | fn do_something(s: String) { 2 | println!("{s}"); 3 | } 4 | 5 | fn main() { 6 | let s = "Hello".to_string(); 7 | do_something(s); 8 | do_something(s); 9 | } -------------------------------------------------------------------------------- /src/borrow_move2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "borrow_move2" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/borrow_move2/src/main.rs: -------------------------------------------------------------------------------- 1 | struct Data(usize); 2 | 3 | impl Drop for Data { 4 | fn drop(&mut self) { 5 | println!("Data object {} is being destroyed", self.0); 6 | } 7 | } 8 | 9 | fn do_something(d: Data) { 10 | println!("Hello data #{}", d.0); 11 | } 12 | 13 | fn main() { 14 | let data = Data(1); 15 | do_something(data); 16 | std::thread::sleep(std::time::Duration::from_secs(5)); 17 | println!("Program ending"); 18 | } 19 | -------------------------------------------------------------------------------- /src/borrow_move3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "borrow_move3" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/borrow_move3/src/main.rs: -------------------------------------------------------------------------------- 1 | struct Data(usize); 2 | 3 | impl Drop for Data { 4 | fn drop(&mut self) { 5 | println!("Data object {} is being destroyed", self.0); 6 | } 7 | } 8 | 9 | fn do_something(d: Data) -> Data { 10 | println!("Hello data #{}", d.0); 11 | d 12 | } 13 | 14 | fn main() { 15 | let data = Data(1); 16 | let data = do_something(data); 17 | do_something(data); 18 | std::thread::sleep(std::time::Duration::from_secs(5)); 19 | println!("Program ending"); 20 | } 21 | -------------------------------------------------------------------------------- /src/borrow_move4/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "borrow_move4" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/borrow_move4/src/main.rs: -------------------------------------------------------------------------------- 1 | #[derive(Default, Clone)] 2 | struct MyBuilder { 3 | a: bool, 4 | } 5 | 6 | impl MyBuilder { 7 | fn with(&mut self, a: bool) -> &mut Self { 8 | self.a = a; 9 | self 10 | } 11 | 12 | fn build() -> &'static mut Self { 13 | Self::default().with(true) 14 | } 15 | } 16 | 17 | fn main() { 18 | let x = MyBuilder::build(); 19 | } 20 | -------------------------------------------------------------------------------- /src/borrow_move5/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "borrow_move5" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/borrow_move5/src/main.rs: -------------------------------------------------------------------------------- 1 | #[derive(Default)] 2 | struct MyBuilder { 3 | a: bool, 4 | } 5 | 6 | impl MyBuilder { 7 | fn with(mut self, a: bool) -> Self { 8 | self.a = a; 9 | self 10 | } 11 | 12 | fn build() -> Self { 13 | Self::default().with(true) 14 | } 15 | } 16 | 17 | fn main() { 18 | let _x = MyBuilder::build(); 19 | } 20 | -------------------------------------------------------------------------------- /src/borrow_oops/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "borrow_oops" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/borrow_oops/src/main.rs: -------------------------------------------------------------------------------- 1 | struct Organization { 2 | pub people: Vec, 3 | } 4 | 5 | struct Person { 6 | pub resources: Vec, 7 | } 8 | 9 | impl Person { 10 | fn give_resource(&mut self, name: &str, org: &mut Organization, recipient: usize) { 11 | if let Some((idx, resource)) = self.resources.iter().enumerate().find(|(_, item)| name == item.name) { 12 | self.resources.remove(idx); 13 | org.people[recipient].resources.push(resource.clone()); 14 | } 15 | } 16 | } 17 | 18 | #[derive(Clone)] 19 | struct Resource { 20 | pub name: String, 21 | } 22 | 23 | fn main() { 24 | let mut org = Organization { 25 | people: vec![ 26 | Person { resources: vec![ Resource { name: "Stapler".to_string() } ]}, 27 | Person { resources: Vec::new() }, 28 | ] 29 | }; 30 | org.people[0].give_resource("Stapler", &mut org, 1); 31 | } 32 | -------------------------------------------------------------------------------- /src/borrow_oops2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "borrow_oops2" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/borrow_oops2/src/main.rs: -------------------------------------------------------------------------------- 1 | struct Organization { 2 | pub people: Vec, 3 | } 4 | 5 | impl Organization { 6 | fn move_resource(&mut self, from: usize, to: usize, name: &str) { 7 | if let Some(resource) = self.people[from].take_resource(name) { 8 | self.people[to].give_resource(resource); 9 | } 10 | } 11 | } 12 | 13 | struct Person { 14 | pub resources: Vec, 15 | } 16 | 17 | impl Person { 18 | fn take_resource(&mut self, name: &str) -> Option { 19 | let index = self.resources.iter().position(|r| r.name == name); 20 | if let Some(index) = index { 21 | let resource = self.resources.remove(index); 22 | Some(resource) 23 | } else { 24 | None 25 | } 26 | } 27 | 28 | fn give_resource(&mut self, resource: Resource) { 29 | self.resources.push(resource); 30 | } 31 | } 32 | 33 | // #[derive(Clone)] // We don't need this anymore 34 | struct Resource { 35 | pub name: String, 36 | } 37 | 38 | fn main() { 39 | let mut org = Organization { 40 | people: vec![ 41 | Person { resources: vec![ Resource { name: "Stapler".to_string() } ]}, 42 | Person { resources: Vec::new() }, 43 | ] 44 | }; 45 | org.move_resource(0, 1, "stapler"); 46 | } 47 | -------------------------------------------------------------------------------- /src/borrow_oops3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "borrow_oops3" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | thiserror = "1" 10 | -------------------------------------------------------------------------------- /src/borrow_oops3/src/main.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | enum OrgError { 5 | #[error("The requested person does not exist")] 6 | PersonDoesNotExist(usize), 7 | #[error("The requested resource is not allocated to the person")] 8 | ResourceNotFound, 9 | } 10 | 11 | struct Organization { 12 | pub people: Vec, 13 | } 14 | 15 | impl Organization { 16 | fn move_resource(&mut self, from: usize, to: usize, name: &str) -> Result<(), OrgError> { 17 | if let (Some(id1), Some(id2)) = (self.find_person(from), self.find_person(2)) { 18 | if let Some(resource) = self.people[id1].take_resource(name) { 19 | self.people[id2].give_resource(resource); 20 | Ok(()) 21 | } else { 22 | Err(OrgError::ResourceNotFound) 23 | } 24 | } else if self.find_person(from).is_none() { 25 | return Err(OrgError::PersonDoesNotExist(from)); 26 | } else { 27 | return Err(OrgError::PersonDoesNotExist(to)); 28 | } 29 | } 30 | 31 | fn find_person(&self, id: usize) -> Option { 32 | self.people.iter().position(|p| p.id == id) 33 | } 34 | } 35 | 36 | struct Person { 37 | id: usize, 38 | pub resources: Vec, 39 | } 40 | 41 | impl Person { 42 | fn take_resource(&mut self, name: &str) -> Option { 43 | let index = self.resources.iter().position(|r| r.name == name); 44 | if let Some(index) = index { 45 | let resource = self.resources.remove(index); 46 | Some(resource) 47 | } else { 48 | None 49 | } 50 | } 51 | 52 | fn give_resource(&mut self, resource: Resource) { 53 | self.resources.push(resource); 54 | } 55 | } 56 | 57 | // #[derive(Clone)] // We don't need this anymore 58 | struct Resource { 59 | pub name: String, 60 | } 61 | 62 | fn main() { 63 | let mut org = Organization { 64 | people: vec![ 65 | Person { id: 0, resources: vec![ Resource { name: "Stapler".to_string() } ]}, 66 | Person { id: 1, resources: Vec::new() }, 67 | ] 68 | }; 69 | org.move_resource(0, 1, "stapler").unwrap(); 70 | } 71 | -------------------------------------------------------------------------------- /src/count_primes/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "count_primes" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/count_primes/src/main.rs: -------------------------------------------------------------------------------- 1 | fn is_prime(n: u32) -> bool { 2 | (2 ..= n/2).all(|i| n % i != 0 ) 3 | } 4 | 5 | const MAX:u32 = 200000; 6 | 7 | fn main() { 8 | let mut count = 0; 9 | let now = std::time::Instant::now(); 10 | for i in 2 .. MAX { 11 | if is_prime(i) { 12 | count += 1; 13 | } 14 | } 15 | let time = now.elapsed(); 16 | println!("Found {count} primes in {} seconds", time.as_secs_f32()); 17 | } 18 | 19 | #[cfg(test)] 20 | mod test { 21 | use super::*; 22 | 23 | #[test] 24 | fn test_first_hundred_primes() { 25 | // List obtained from: https://en.wikipedia.org/wiki/Prime_number 26 | let primes: Vec = (2..100).filter(|n| is_prime(*n)).collect(); 27 | assert_eq!( 28 | primes, 29 | [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97] 30 | ); 31 | } 32 | } -------------------------------------------------------------------------------- /src/count_primes_atomic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "count_primes_atomic" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/count_primes_atomic/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::AtomicUsize; 2 | 3 | fn is_prime(n: u32) -> bool { 4 | (2 ..= n/2).all(|i| n % i != 0 ) 5 | } 6 | 7 | fn main() { 8 | const MAX: u32 = 200_000; 9 | static COUNTER: AtomicUsize = AtomicUsize::new(0); 10 | let now = std::time::Instant::now(); 11 | let t1 = std::thread::spawn(|| { 12 | COUNTER.fetch_add( 13 | (2 .. MAX/2).filter(|n| is_prime(*n)).count(), 14 | std::sync::atomic::Ordering::Relaxed 15 | ); 16 | }); 17 | let t2 = std::thread::spawn(|| { 18 | COUNTER.fetch_add( 19 | (MAX/2 .. MAX).filter(|n| is_prime(*n)).count(), 20 | std::sync::atomic::Ordering::Relaxed 21 | ); 22 | }); 23 | t1.join(); 24 | t2.join(); 25 | let duration = now.elapsed(); 26 | println!("Found {} prime numbers in the range 2..{MAX}", COUNTER.load(std::sync::atomic::Ordering::Relaxed)); 27 | println!("Execution took {} seconds", duration.as_secs_f32()); 28 | } -------------------------------------------------------------------------------- /src/count_primes_atomic_many/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "count_primes_atomic_many" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/count_primes_atomic_many/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::AtomicUsize; 2 | 3 | fn is_prime(n: u32) -> bool { 4 | (2 ..= n/2).all(|i| n % i != 0 ) 5 | } 6 | 7 | fn main() { 8 | const MAX: u32 = 200_000; 9 | const N_THREADS: u32 = 8; 10 | 11 | static COUNTER: AtomicUsize = AtomicUsize::new(0); 12 | 13 | // Hold thread handles 14 | let mut threads = Vec::with_capacity(N_THREADS as usize); 15 | 16 | // Generate all the numbers we want to check 17 | let group = MAX / N_THREADS; 18 | 19 | let now = std::time::Instant::now(); 20 | 21 | for i in 0 .. N_THREADS { 22 | let counter = i; 23 | threads.push(std::thread::spawn(move || { 24 | let range = u32::max(2, counter*group) .. (i+1)*group; 25 | COUNTER.fetch_add( 26 | range.filter(|n| is_prime(*n)).count(), 27 | std::sync::atomic::Ordering::Relaxed 28 | ); 29 | })); 30 | } 31 | 32 | for thread in threads { 33 | let _ = thread.join(); 34 | } 35 | 36 | let duration = now.elapsed(); 37 | println!("Found {} prime numbers in the range 2..{MAX}", COUNTER.load(std::sync::atomic::Ordering::Relaxed)); 38 | println!("Execution took {} seconds", duration.as_secs_f32()); 39 | } -------------------------------------------------------------------------------- /src/count_primes_bad/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "count_primes_bad" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/count_primes_bad/src/main.rs: -------------------------------------------------------------------------------- 1 | fn is_prime(n: u32) -> bool { 2 | (2 ..= n/2).all(|i| n % i != 0 ) 3 | } 4 | 5 | fn main() { 6 | const MAX: u32 = 200_000; 7 | let mut counter = 0; 8 | let t1 = std::thread::spawn(|| { 9 | counter += (2 .. MAX/2).filter(|n| is_prime(*n)).count(); 10 | }); 11 | let t2 = std::thread::spawn(|| { 12 | counter += (MAX/2 .. MAX).filter(|n| is_prime(*n)).count(); 13 | }); 14 | t1.join(); 15 | t2.join(); 16 | println!("Found {counter} prime numbers in the range 2..{MAX}"); 17 | } -------------------------------------------------------------------------------- /src/count_primes_rayon/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "count_primes_rayon" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rayon = "1.6.1" 10 | -------------------------------------------------------------------------------- /src/count_primes_rayon/src/main.rs: -------------------------------------------------------------------------------- 1 | fn is_prime(n: u32) -> bool { 2 | (2 ..= n/2).all(|i| n % i != 0 ) 3 | } 4 | 5 | fn main() { 6 | const MAX:u32 = 200000; 7 | let now = std::time::Instant::now(); 8 | 9 | let count = (2..MAX) 10 | .filter(|n| is_prime(*n)) 11 | .count(); 12 | 13 | let duration = now.elapsed(); 14 | println!("Found {count} primes in {} seconds", duration.as_secs_f32()); 15 | } 16 | -------------------------------------------------------------------------------- /src/count_primes_rayon2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "count_primes_rayon2" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rayon = "1.6.1" 10 | -------------------------------------------------------------------------------- /src/count_primes_rayon2/src/main.rs: -------------------------------------------------------------------------------- 1 | use rayon::prelude::{IntoParallelIterator, ParallelIterator}; 2 | 3 | fn is_prime(n: u32) -> bool { 4 | (2 ..= n/2).all(|i| n % i != 0 ) 5 | } 6 | 7 | fn main() { 8 | const MAX:u32 = 200000; 9 | let now = std::time::Instant::now(); 10 | 11 | let count = (2..MAX) 12 | .into_par_iter() 13 | .filter(|n| is_prime(*n)) 14 | .count(); 15 | 16 | let duration = now.elapsed(); 17 | println!("Found {count} primes in {} seconds", duration.as_secs_f32()); 18 | } 19 | -------------------------------------------------------------------------------- /src/count_primes_shared/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "count_primes_shared" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/count_primes_shared/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | 3 | fn is_prime(n: u32) -> bool { 4 | (2 ..= n/2).all(|i| n % i != 0 ) 5 | } 6 | 7 | fn main() { 8 | const MAX: u32 = 200_000; 9 | const N_THREADS: u32 = 8; 10 | 11 | static PRIMES: Mutex> = Mutex::new(Vec::new()); 12 | 13 | // Hold thread handles 14 | let mut threads = Vec::with_capacity(N_THREADS as usize); 15 | 16 | // Generate all the numbers we want to check 17 | let group = MAX / N_THREADS; 18 | 19 | let now = std::time::Instant::now(); 20 | 21 | for i in 0 .. N_THREADS { 22 | let counter = i; 23 | threads.push(std::thread::spawn(move || { 24 | let range = u32::max(2, counter*group) .. (i+1)*group; 25 | let my_primes: Vec = range.filter(|n| is_prime(*n)).collect(); 26 | PRIMES.lock().unwrap().extend(my_primes); 27 | })); 28 | } 29 | 30 | for thread in threads { 31 | let _ = thread.join(); 32 | } 33 | 34 | let duration = now.elapsed(); 35 | println!("Found {} prime numbers in the range 2..{MAX}", PRIMES.lock().unwrap().len()); 36 | println!("Execution took {} seconds", duration.as_secs_f32()); 37 | } -------------------------------------------------------------------------------- /src/count_primes_shared2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo_primes_shared2" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/count_primes_shared2/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::thread::JoinHandle; 2 | 3 | fn is_prime(n: u32) -> bool { 4 | (2 ..= n/2).all(|i| n % i != 0 ) 5 | } 6 | 7 | fn main() { 8 | const MAX: u32 = 200_000; 9 | const N_THREADS: u32 = 8; 10 | 11 | // Hold thread handles 12 | let mut threads: Vec>> = Vec::with_capacity(N_THREADS as usize); 13 | 14 | // Generate all the numbers we want to check 15 | let group = MAX / N_THREADS; 16 | 17 | let now = std::time::Instant::now(); 18 | 19 | for i in 0 .. N_THREADS { 20 | let counter = i; 21 | threads.push(std::thread::spawn(move || { 22 | let range = u32::max(2, counter*group) .. (i+1)*group; 23 | range.filter(|n| is_prime(*n)).collect() 24 | })); 25 | } 26 | 27 | let mut primes = Vec::new(); 28 | for thread in threads { 29 | if let Ok(new_primes) = thread.join() { 30 | primes.extend(new_primes); 31 | } else { 32 | println!("Something went wrong"); 33 | } 34 | } 35 | 36 | let duration = now.elapsed(); 37 | println!("Found {} prime numbers in the range 2..{MAX}", primes.len()); 38 | println!("Execution took {} seconds", duration.as_secs_f32()); 39 | } -------------------------------------------------------------------------------- /src/cpp/Primes/.vs/Primes/v17/.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/src/cpp/Primes/.vs/Primes/v17/.suo -------------------------------------------------------------------------------- /src/cpp/Primes/.vs/Primes/v17/Browse.VC.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/src/cpp/Primes/.vs/Primes/v17/Browse.VC.db -------------------------------------------------------------------------------- /src/cpp/Primes/.vs/Primes/v17/ipch/AutoPCH/d63b32b54c0f111b/PRIMES.ipch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/src/cpp/Primes/.vs/Primes/v17/ipch/AutoPCH/d63b32b54c0f111b/PRIMES.ipch -------------------------------------------------------------------------------- /src/cpp/Primes/.vs/Primes/v17/ipch/AutoPCH/e71747352f44b5a9/PRIMES.ipch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/src/cpp/Primes/.vs/Primes/v17/ipch/AutoPCH/e71747352f44b5a9/PRIMES.ipch -------------------------------------------------------------------------------- /src/cpp/Primes/Primes.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | bool is_prime(uint32_t n) { 5 | for (uint32_t i = 2; i <= n / 2; ++i) { 6 | if (n % i == 0) return false; 7 | } 8 | return true; 9 | } 10 | 11 | int main() { 12 | const auto max = 200000; 13 | uint32_t count = 0; 14 | std::thread t1([&count, max]() { 15 | for (auto i = 2; i < max / 2; i++) { 16 | if (is_prime(i)) ++count; 17 | } 18 | }); 19 | std::thread t2([&count, max]() { 20 | for (auto i = max / 2; i < max; i++) { 21 | if (is_prime(i)) ++count; 22 | } 23 | }); 24 | t1.join(); 25 | t2.join(); 26 | std::cout << "Found " << count << " prime numbers.\n"; 27 | } -------------------------------------------------------------------------------- /src/cpp/Primes/Primes.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32804.467 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Primes", "Primes.vcxproj", "{412EE107-FE52-4D71-A07C-85A68F469A92}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {412EE107-FE52-4D71-A07C-85A68F469A92}.Debug|x64.ActiveCfg = Debug|x64 17 | {412EE107-FE52-4D71-A07C-85A68F469A92}.Debug|x64.Build.0 = Debug|x64 18 | {412EE107-FE52-4D71-A07C-85A68F469A92}.Debug|x86.ActiveCfg = Debug|Win32 19 | {412EE107-FE52-4D71-A07C-85A68F469A92}.Debug|x86.Build.0 = Debug|Win32 20 | {412EE107-FE52-4D71-A07C-85A68F469A92}.Release|x64.ActiveCfg = Release|x64 21 | {412EE107-FE52-4D71-A07C-85A68F469A92}.Release|x64.Build.0 = Release|x64 22 | {412EE107-FE52-4D71-A07C-85A68F469A92}.Release|x86.ActiveCfg = Release|Win32 23 | {412EE107-FE52-4D71-A07C-85A68F469A92}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {EB4B9FC3-165F-4AC2-BF7A-88E75A5C3CC8} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/cpp/Primes/Primes.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/cpp/Primes/Primes.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/cpp/Primes/x64/Release/Primes.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/src/cpp/Primes/x64/Release/Primes.exe -------------------------------------------------------------------------------- /src/cpp/Primes/x64/Release/Primes.exe.recipe: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | C:\Users\Herbert\Documents\Ardan\Rust Foundations 4 Day\src\cpp\Primes\x64\Release\Primes.exe 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/cpp/Primes/x64/Release/Primes.iobj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/src/cpp/Primes/x64/Release/Primes.iobj -------------------------------------------------------------------------------- /src/cpp/Primes/x64/Release/Primes.ipdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/src/cpp/Primes/x64/Release/Primes.ipdb -------------------------------------------------------------------------------- /src/cpp/Primes/x64/Release/Primes.log: -------------------------------------------------------------------------------- 1 |  Primes.cpp 2 | Generating code 3 | Previous IPDB not found, fall back to full compilation. 4 | All 73 functions were compiled because no usable IPDB/IOBJ from previous compilation was found. 5 | Finished generating code 6 | Primes.vcxproj -> C:\Users\Herbert\Documents\Ardan\Rust Foundations 4 Day\src\cpp\Primes\x64\Release\Primes.exe 7 | -------------------------------------------------------------------------------- /src/cpp/Primes/x64/Release/Primes.obj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/src/cpp/Primes/x64/Release/Primes.obj -------------------------------------------------------------------------------- /src/cpp/Primes/x64/Release/Primes.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/src/cpp/Primes/x64/Release/Primes.pdb -------------------------------------------------------------------------------- /src/cpp/Primes/x64/Release/Primes.tlog/CL.command.1.tlog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/src/cpp/Primes/x64/Release/Primes.tlog/CL.command.1.tlog -------------------------------------------------------------------------------- /src/cpp/Primes/x64/Release/Primes.tlog/CL.read.1.tlog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/src/cpp/Primes/x64/Release/Primes.tlog/CL.read.1.tlog -------------------------------------------------------------------------------- /src/cpp/Primes/x64/Release/Primes.tlog/CL.write.1.tlog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/src/cpp/Primes/x64/Release/Primes.tlog/CL.write.1.tlog -------------------------------------------------------------------------------- /src/cpp/Primes/x64/Release/Primes.tlog/Primes.lastbuildstate: -------------------------------------------------------------------------------- 1 | PlatformToolSet=v143:VCToolArchitecture=Native64Bit:VCToolsVersion=14.33.31629:TargetPlatformVersion=10.0.19041.0:VcpkgTriplet=x64-windows: 2 | Release|x64|C:\Users\Herbert\Documents\Ardan\Rust Foundations 4 Day\src\cpp\Primes\| 3 | -------------------------------------------------------------------------------- /src/cpp/Primes/x64/Release/Primes.tlog/Primes.write.1u.tlog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/src/cpp/Primes/x64/Release/Primes.tlog/Primes.write.1u.tlog -------------------------------------------------------------------------------- /src/cpp/Primes/x64/Release/Primes.tlog/link.command.1.tlog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/src/cpp/Primes/x64/Release/Primes.tlog/link.command.1.tlog -------------------------------------------------------------------------------- /src/cpp/Primes/x64/Release/Primes.tlog/link.read.1.tlog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/src/cpp/Primes/x64/Release/Primes.tlog/link.read.1.tlog -------------------------------------------------------------------------------- /src/cpp/Primes/x64/Release/Primes.tlog/link.write.1.tlog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/src/cpp/Primes/x64/Release/Primes.tlog/link.write.1.tlog -------------------------------------------------------------------------------- /src/cpp/Primes/x64/Release/Primes.vcxproj.FileListAbsolute.txt: -------------------------------------------------------------------------------- 1 | C:\Users\Herbert\Documents\Ardan\Rust Foundations 4 Day\src\cpp\Primes\x64\Release\Primes.exe 2 | -------------------------------------------------------------------------------- /src/cpp/Primes/x64/Release/vc143.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebracket/ArdanUltimateRustFoundations/9ba073f41341618e278769420a65072046e2c121/src/cpp/Primes/x64/Release/vc143.pdb -------------------------------------------------------------------------------- /src/cpp/Primes/x64/Release/vcpkg.applocal.log: -------------------------------------------------------------------------------- 1 |  2 | -------------------------------------------------------------------------------- /src/dashmap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dashmap_demo" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | dashmap = "5.4.0" 10 | once_cell = "1.17.1" 11 | -------------------------------------------------------------------------------- /src/dashmap/src/main.rs: -------------------------------------------------------------------------------- 1 | use dashmap::DashMap; 2 | use once_cell::sync::Lazy; 3 | use std::{thread, time::Duration}; 4 | 5 | static MAP: Lazy> = Lazy::new(DashMap::new); 6 | 7 | fn main() { 8 | let mut threads = Vec::new(); 9 | 10 | // Adder Threads 11 | for i in 0..10 { 12 | threads.push(thread::spawn(move || { 13 | for _ in 0..100 { 14 | if let Some(mut count) = MAP.get_mut(&i) { 15 | *count += 1; 16 | } else { 17 | MAP.insert(i, 1); 18 | } 19 | std::thread::sleep(Duration::from_secs_f32(0.1)); 20 | } 21 | })); 22 | } 23 | 24 | // Reader Threads 25 | for i in 0..10 { 26 | threads.push(thread::spawn(move || { 27 | for _ in 0..20 { 28 | if let Some(count) = MAP.get(&i) { 29 | println!("Count of {i}: {}", *count); 30 | std::thread::sleep(Duration::from_secs_f32(0.5)); 31 | } 32 | } 33 | })); 34 | } 35 | 36 | for t in threads { 37 | let _ = t.join(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/docs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "docs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | serde = { version = "1.0.152", features = [ "derive" ] } 10 | serde_json = "1.0.92" 11 | sha2 = "0" 12 | -------------------------------------------------------------------------------- /src/docs/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_docs)] 2 | //! # Authentication 3 | //! 4 | //! This module provides user validation and role assignment. 5 | 6 | mod user; 7 | mod login_action; 8 | use std::collections::HashMap; 9 | pub use user::User; 10 | pub use login_action::*; 11 | 12 | pub mod serde { 13 | pub use serde::*; 14 | } 15 | 16 | /// `hash_password` applies a SHA256 digest hash to a string, and returns a 17 | /// string containing the hashed string, in hexadecimal format. 18 | /// 19 | /// ## Arguments 20 | /// 21 | /// * `password` - the password to hash. 22 | /// 23 | /// ## Example 24 | /// 25 | /// ``` 26 | /// use docs::hash_password; 27 | /// println!("{}", hash_password("test")); 28 | /// ``` 29 | pub fn hash_password(password: &str) -> String { 30 | use sha2::Digest; 31 | let mut hasher = sha2::Sha256::new(); 32 | hasher.update(password); 33 | format!("{:X}", hasher.finalize()) 34 | } 35 | 36 | pub fn build_users_file() { 37 | use std::io::Write; 38 | 39 | let users = get_users_old(); 40 | //let json = serde_json::to_string(&users).unwrap(); 41 | let json = serde_json::to_string_pretty(&users).unwrap(); 42 | let mut f = std::fs::File::create("users.json").unwrap(); 43 | f.write_all(json.as_bytes()).unwrap(); 44 | } 45 | 46 | pub fn save_users_file(users: &HashMap) { 47 | use std::io::Write; 48 | let json = serde_json::to_string_pretty(&users).unwrap(); 49 | let mut f = std::fs::File::create("users.json").unwrap(); 50 | f.write_all(json.as_bytes()).unwrap(); 51 | } 52 | 53 | #[allow(dead_code)] 54 | fn get_users_old() -> HashMap { 55 | /*let mut result = HashMap::new(); 56 | result.insert("herbert".to_string(), User::new("herbert", "password", LoginAction::Accept(Role::Admin))); 57 | result*/ 58 | let mut users = vec![ 59 | User::new("herbert", "password", LoginAction::Accept(Role::Admin)), 60 | User::new("bob", "password", LoginAction::Accept(Role::User)), 61 | User::new("fred", "password", LoginAction::Denied(DeniedReason::PasswordExpired)), 62 | ]; 63 | /*users 64 | .iter() // Create an iterator 65 | .map(|user| (user.username.clone(), user.clone()) ) 66 | .collect()*/ 67 | users 68 | .drain(0..) 69 | .map(|user| ( user.username.clone(), user )) 70 | .collect() 71 | } 72 | 73 | pub fn get_users() -> HashMap { 74 | let json = std::fs::read_to_string("users.json").unwrap(); 75 | serde_json::from_str(&json).unwrap() 76 | } 77 | 78 | pub fn login(users: &HashMap, username: &str, password: &str) -> Option { 79 | let username = username.trim().to_lowercase(); 80 | let password = hash_password(password.trim()); 81 | 82 | users 83 | .get(&username) 84 | .filter(|user| user.password == password) 85 | .map(|user| user.action.clone()) 86 | } 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/docs/src/login_action/action.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize}; 2 | use crate::{Role, DeniedReason}; 3 | 4 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 5 | pub enum LoginAction { 6 | Accept(Role), 7 | Denied(DeniedReason), 8 | } 9 | 10 | impl LoginAction { 11 | pub fn do_login(&self, on_success: fn(&Role), on_denied: fn(&DeniedReason)) { 12 | match self { 13 | Self::Accept(role) => on_success(role), 14 | Self::Denied(reason) => on_denied(reason), 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/docs/src/login_action/denied_reason.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize}; 2 | 3 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 4 | pub enum DeniedReason { 5 | PasswordExpired, 6 | AccountLocked{reason: String}, 7 | } -------------------------------------------------------------------------------- /src/docs/src/login_action/mod.rs: -------------------------------------------------------------------------------- 1 | mod denied_reason; 2 | mod action; 3 | mod role; 4 | 5 | pub use denied_reason::DeniedReason; 6 | pub use action::LoginAction; 7 | pub use role::Role; 8 | 9 | -------------------------------------------------------------------------------- /src/docs/src/login_action/role.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize}; 2 | 3 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 4 | pub enum Role { 5 | Admin, 6 | User, 7 | Limited 8 | } -------------------------------------------------------------------------------- /src/docs/src/user.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize}; 2 | use crate::{LoginAction, hash_password}; 3 | 4 | #[derive(Clone, Debug, Serialize, Deserialize)] 5 | pub struct User { 6 | pub username: String, 7 | pub(crate) password: String, 8 | pub action: LoginAction, 9 | } 10 | 11 | impl User { 12 | pub fn new(username: &str, password: &str, action: LoginAction) -> Self { 13 | Self { 14 | username: username.to_string(), 15 | password: hash_password(password), 16 | action 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/docs_exe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "docs_exe" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | docs = { path = "../docs" } -------------------------------------------------------------------------------- /src/docs_exe/src/main.rs: -------------------------------------------------------------------------------- 1 | use docs::*; 2 | 3 | fn user_accepted(role: &Role) { 4 | println!("You are logged in as a {role:?}"); 5 | } 6 | 7 | fn main() { 8 | //build_users_file(); 9 | let users = get_users(); 10 | 11 | println!("Welcome to the (Not Very) Secure Server"); 12 | println!("Enter your username:"); 13 | let mut username = String::new(); 14 | let mut password = String::new(); 15 | let stdin = std::io::stdin(); 16 | stdin.read_line(&mut username).unwrap(); 17 | println!("Enter your password:"); 18 | stdin.read_line(&mut password).unwrap(); 19 | 20 | match login(&users, &username, &password) { 21 | None => { 22 | println!("{} is not a known user.", username.trim()); 23 | println!("This is where we handle new users."); 24 | } 25 | Some(login_action) => { 26 | login_action.do_login( 27 | user_accepted, 28 | |reason| { 29 | println!("Access denied"); 30 | println!("{reason:?}"); 31 | } 32 | ) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/errors1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "errors1" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/errors1/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::error; 2 | type Result = std::result::Result>; 3 | 4 | fn get_line_from_keyboard() -> Result { 5 | let mut input = String::new(); 6 | let stdin = std::io::stdin(); 7 | stdin.read_line(&mut input)?; 8 | let trimmed = input.trim(); 9 | Ok(trimmed.to_string()) 10 | } 11 | 12 | fn get_int_from_keyboard() -> Result { 13 | let text = get_line_from_keyboard()?; 14 | Ok(text.trim().parse()?) 15 | } 16 | 17 | fn main() { 18 | loop { 19 | println!("Enter an integer:"); 20 | let number = get_int_from_keyboard(); 21 | match number { 22 | Ok(n) => { println!("You entered {n}"); break; }, 23 | Err(e) => println!("Error: {e:?}"), 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/errors2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "errors2" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1" 10 | -------------------------------------------------------------------------------- /src/errors2/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | fn get_line_from_keyboard() -> Result { 4 | let mut input = String::new(); 5 | let stdin = std::io::stdin(); 6 | stdin.read_line(&mut input)?; 7 | let trimmed = input.trim(); 8 | Ok(trimmed.to_string()) 9 | } 10 | 11 | fn get_int_from_keyboard() -> Result { 12 | let text = get_line_from_keyboard()?; 13 | Ok(text.trim().parse()?) 14 | } 15 | 16 | fn main() { 17 | loop { 18 | println!("Enter an integer:"); 19 | let number = get_int_from_keyboard(); 20 | match number { 21 | Ok(n) => { println!("You entered {n}"); break; }, 22 | Err(e) => { 23 | if let Some(std::io::Error { .. }) = e.downcast_ref::() { 24 | panic!("stdin is unavailable"); 25 | } else { 26 | println!("Try again - that wasn't an integer"); 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/errors3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "errors3" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | thiserror = "1" -------------------------------------------------------------------------------- /src/errors3/src/main.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | enum InputError { 5 | #[error("Standard input is unavailable")] 6 | StdIn, 7 | 8 | #[error("Cannot parse integer from text")] 9 | NotAnInteger, 10 | } 11 | 12 | fn get_line_from_keyboard() -> Result { 13 | let mut input = String::new(); 14 | let stdin = std::io::stdin(); 15 | stdin.read_line(&mut input).map_err(|_| InputError::StdIn)?; 16 | let trimmed = input.trim(); 17 | Ok(trimmed.to_string()) 18 | } 19 | 20 | fn get_int_from_keyboard() -> Result { 21 | let text = get_line_from_keyboard()?; 22 | text.trim().parse().map_err(|_| InputError::NotAnInteger) 23 | } 24 | 25 | fn main() { 26 | loop { 27 | println!("Enter an integer:"); 28 | let number = get_int_from_keyboard(); 29 | match number { 30 | Ok(n) => { println!("You entered {n}"); break; }, 31 | Err(InputError::StdIn) => panic!("Input doesn't work"), 32 | Err(InputError::NotAnInteger) => println!("Please try again"), 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/file_lock/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "file_lock" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/file_lock/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::remove_file; 2 | use std::time::Duration; 3 | use std::{path::Path, fs::File}; 4 | use std::io::Write; 5 | 6 | struct FileLock; 7 | 8 | impl FileLock { 9 | fn new() -> Self { 10 | let path = Path::new("file.lock"); 11 | if path.exists() { 12 | panic!("You can't run this program more than once"); 13 | } 14 | let mut output = File::create(path).unwrap(); 15 | write!(output, "locked").unwrap(); 16 | 17 | Self 18 | } 19 | } 20 | 21 | impl Drop for FileLock { 22 | fn drop(&mut self) { 23 | let path = Path::new("file.lock"); 24 | remove_file(path).unwrap(); 25 | } 26 | } 27 | 28 | fn main() { 29 | let _lock = FileLock::new(); 30 | // Pretend to do something important 31 | std::thread::sleep(Duration::from_secs(30)); 32 | } 33 | -------------------------------------------------------------------------------- /src/flags_exe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "flags_exe" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | flags_lib = { path = "../flags_lib", default_features = false, features = [ "other" ] } 10 | -------------------------------------------------------------------------------- /src/flags_exe/src/main.rs: -------------------------------------------------------------------------------- 1 | use flags_lib::MODE; 2 | 3 | fn main() { 4 | println!("{MODE}"); 5 | } 6 | -------------------------------------------------------------------------------- /src/flags_lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "flags_lib" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | default = [ "normal" ] 8 | normal = [] 9 | other = [] 10 | 11 | [dependencies] 12 | -------------------------------------------------------------------------------- /src/flags_lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(not(feature = "other"), feature = "normal"))] 2 | pub const MODE: &str = "NORMAL"; 3 | 4 | #[cfg(all(not(feature = "normal"), feature = "other"))] 5 | pub const MODE: &str = "OTHER"; -------------------------------------------------------------------------------- /src/generic_data/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generic_data" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/generic_data/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Index; 2 | 3 | #[derive(Debug)] 4 | struct StableVec { 5 | data: Vec> 6 | } 7 | 8 | impl StableVec { 9 | fn new() -> Self { 10 | Self { 11 | data: Vec::new(), 12 | } 13 | } 14 | 15 | fn push(&mut self, item: T) -> usize { 16 | let id = self.data.len(); 17 | self.data.push(Some(item)); 18 | id 19 | } 20 | 21 | fn remove(&mut self, id: usize) { 22 | self.data[id] = None; 23 | } 24 | 25 | fn get(&self, id: usize) -> &Option { 26 | &self.data[id] 27 | } 28 | } 29 | 30 | impl Index for StableVec { 31 | type Output = Option; 32 | fn index(&self, index: usize) -> &Self::Output { 33 | &self.data[index] 34 | } 35 | } 36 | 37 | fn main() { 38 | let mut store = StableVec::::new(); 39 | let a = store.push("A".to_string()); 40 | let b = store.push("B".to_string()); 41 | let c = store.push("C".to_string()); 42 | store.remove(b); 43 | println!("{:?}", store.get(a)); 44 | println!("{:?}", store.get(b)); 45 | println!("{:?}", store.get(c)); 46 | println!("{:?}", store[c]); 47 | } 48 | -------------------------------------------------------------------------------- /src/generic_data_complex/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generic_data_complex" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/generic_data_complex/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, hash::Hash, fmt::Debug}; 2 | 3 | #[derive(Debug)] 4 | struct HashSetData 5 | where KEY: Eq + Hash + std::fmt::Display, VALUE: Debug + Sensor 6 | { 7 | data: HashMap> 8 | } 9 | 10 | impl HashSetData 11 | where KEY: Eq + Hash + std::fmt::Display, VALUE: Debug + Sensor 12 | { 13 | fn new() -> Self { 14 | Self { 15 | data: HashMap::new() 16 | } 17 | } 18 | 19 | fn add_reading(&mut self, key: KEY, reading: VALUE) { 20 | if let Some(entry) = self.data.get_mut(&key) { 21 | entry.push(reading); 22 | } else { 23 | self.data.insert(key, vec![reading]); 24 | } 25 | } 26 | 27 | fn print_results(&self) { 28 | for (key, value) in self.data.iter() { 29 | let sum: i32 = value.iter().map(|r| r.reading()).sum(); 30 | let avg = sum / value.len() as i32; 31 | println!("{key} : {avg}"); 32 | } 33 | } 34 | } 35 | 36 | trait Sensor { 37 | fn reading(&self) -> i32; 38 | } 39 | 40 | #[derive(Debug)] 41 | struct Data(i32); 42 | 43 | impl Sensor for Data { 44 | fn reading(&self) -> i32 { 45 | self.0 46 | } 47 | } 48 | 49 | fn main() { 50 | let mut readings = HashSetData::::new(); 51 | readings.add_reading(1, Data(-2)); 52 | readings.add_reading(1, Data(3)); 53 | readings.add_reading(1, Data(5)); 54 | readings.add_reading(2, Data(1)); 55 | readings.print_results(); 56 | } 57 | -------------------------------------------------------------------------------- /src/globals/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "globals" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | once_cell = "1.17.0" 10 | -------------------------------------------------------------------------------- /src/globals/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | 3 | use once_cell::sync::Lazy; 4 | 5 | struct MyType(usize); 6 | 7 | impl MyType { 8 | fn new(n: usize) -> Self { 9 | Self(n) 10 | } 11 | } 12 | 13 | impl Drop for MyType { 14 | fn drop(&mut self) { 15 | println!("Drop"); 16 | } 17 | } 18 | 19 | static SHARED: Lazy> = Lazy::new(|| Mutex::new(MyType::new(5))); 20 | 21 | fn main() { 22 | println!("{}", SHARED.lock().unwrap().0); 23 | } 24 | -------------------------------------------------------------------------------- /src/hello_as_a_service/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello_as_a_service" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/hello_as_a_service/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn greet_user(name: &str) -> String { 2 | format!("Hello {name}") 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | 9 | #[test] 10 | fn test_greet_user() { 11 | assert_eq!("Hello Herbert", greet_user("Herbert")); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/hello_auth/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello_auth" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/hello_auth/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn add(left: usize, right: usize) -> usize { 2 | left + right 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | 9 | #[test] 10 | fn it_works() { 11 | let result = add(2, 2); 12 | assert_eq!(result, 4); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/hello_login_system/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello_login_system" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/hello_login_system/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /src/hello_service_exe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello_service_exe" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | hello_as_a_service = { path = "../hello_as_a_service" } -------------------------------------------------------------------------------- /src/hello_service_exe/src/main.rs: -------------------------------------------------------------------------------- 1 | use hello_as_a_service::greet_user; 2 | 3 | fn main() { 4 | println!("{}", greet_user("Herbert")); 5 | } -------------------------------------------------------------------------------- /src/hello_tokio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello_tokio" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.69" 10 | tokio = { version = "1.25.0", features = ["full"] } 11 | -------------------------------------------------------------------------------- /src/hello_tokio/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use tokio::{join, spawn, task::spawn_blocking}; 4 | 5 | async fn hello(n: u32) { 6 | println!("Hello {n}"); 7 | if n < 10 { 8 | spawn(hello_child(n*10)); 9 | } 10 | } 11 | 12 | async fn hello_child(n: u32) { 13 | println!("Hello again {n}"); 14 | let _ = spawn_blocking(|| std::thread::sleep(Duration::from_secs(1))).await; 15 | tokio::time::sleep(Duration::from_secs(1)).await; 16 | } 17 | 18 | #[tokio::main] 19 | async fn main() -> anyhow::Result<()> { 20 | join!( 21 | hello(1), hello(2), hello(3), hello(4) 22 | ); 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /src/lifetime_func/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lifetime_func" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/lifetime_func/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /src/macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "macros" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/macros/src/main.rs: -------------------------------------------------------------------------------- 1 | macro_rules! really_push { 2 | ($target: expr, $val: expr) => { 3 | $target.push($val); 4 | }; 5 | } 6 | 7 | #[macro_export] 8 | macro_rules! push { 9 | ($target: expr, $($val: expr),+) => { 10 | $( 11 | really_push!($target, $val); 12 | )+ 13 | }; 14 | } 15 | 16 | fn main() { 17 | let mut vec = Vec::new(); 18 | push!(vec, 1, 2, 3, 4); 19 | println!("{:?}", vec); 20 | } 21 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("You probably wanted to run one of the nested workspaces."); 3 | } -------------------------------------------------------------------------------- /src/modules1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "modules1" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | serde = { version = "1.0.152", features = [ "derive" ] } 10 | serde_json = "1.0.92" 11 | sha2 = "0" 12 | -------------------------------------------------------------------------------- /src/modules1/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use serde::{Serialize, Deserialize}; 3 | pub use user::User; 4 | 5 | mod user { 6 | use serde::{Serialize, Deserialize}; 7 | use crate::{LoginAction, hash_password}; 8 | 9 | #[derive(Clone, Debug, Serialize, Deserialize)] 10 | pub struct User { 11 | pub username: String, 12 | pub(crate) password: String, 13 | pub action: LoginAction, 14 | } 15 | 16 | impl User { 17 | pub fn new(username: &str, password: &str, action: LoginAction) -> Self { 18 | Self { 19 | username: username.to_string(), 20 | password: hash_password(password), 21 | action 22 | } 23 | } 24 | } 25 | } 26 | 27 | pub fn hash_password(password: &str) -> String { 28 | use sha2::Digest; 29 | let mut hasher = sha2::Sha256::new(); 30 | hasher.update(password); 31 | format!("{:X}", hasher.finalize()) 32 | } 33 | 34 | pub fn build_users_file() { 35 | use std::io::Write; 36 | 37 | let users = get_users_old(); 38 | //let json = serde_json::to_string(&users).unwrap(); 39 | let json = serde_json::to_string_pretty(&users).unwrap(); 40 | let mut f = std::fs::File::create("users.json").unwrap(); 41 | f.write_all(json.as_bytes()).unwrap(); 42 | } 43 | 44 | pub fn save_users_file(users: &HashMap) { 45 | use std::io::Write; 46 | let json = serde_json::to_string_pretty(&users).unwrap(); 47 | let mut f = std::fs::File::create("users.json").unwrap(); 48 | f.write_all(json.as_bytes()).unwrap(); 49 | } 50 | 51 | #[allow(dead_code)] 52 | fn get_users_old() -> HashMap { 53 | /*let mut result = HashMap::new(); 54 | result.insert("herbert".to_string(), User::new("herbert", "password", LoginAction::Accept(Role::Admin))); 55 | result*/ 56 | let mut users = vec![ 57 | User::new("herbert", "password", LoginAction::Accept(Role::Admin)), 58 | User::new("bob", "password", LoginAction::Accept(Role::User)), 59 | User::new("fred", "password", LoginAction::Denied(DeniedReason::PasswordExpired)), 60 | ]; 61 | /*users 62 | .iter() // Create an iterator 63 | .map(|user| (user.username.clone(), user.clone()) ) 64 | .collect()*/ 65 | users 66 | .drain(0..) 67 | .map(|user| ( user.username.clone(), user )) 68 | .collect() 69 | } 70 | 71 | pub fn get_users() -> HashMap { 72 | let json = std::fs::read_to_string("users.json").unwrap(); 73 | serde_json::from_str(&json).unwrap() 74 | } 75 | 76 | pub fn login(users: &HashMap, username: &str, password: &str) -> Option { 77 | let username = username.trim().to_lowercase(); 78 | let password = hash_password(password.trim()); 79 | 80 | users 81 | .get(&username) 82 | .filter(|user| user.password == password) 83 | .map(|user| user.action.clone()) 84 | } 85 | 86 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 87 | pub enum Role { 88 | Admin, 89 | User, 90 | Limited 91 | } 92 | 93 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 94 | pub enum DeniedReason { 95 | PasswordExpired, 96 | AccountLocked{reason: String}, 97 | } 98 | 99 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 100 | pub enum LoginAction { 101 | Accept(Role), 102 | Denied(DeniedReason), 103 | } 104 | 105 | impl LoginAction { 106 | pub fn do_login(&self, on_success: fn(&Role), on_denied: fn(&DeniedReason)) { 107 | match self { 108 | Self::Accept(role) => on_success(role), 109 | Self::Denied(reason) => on_denied(reason), 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/modules1_exe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "modules1_exe" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | modules1 = { path = "../modules1" } -------------------------------------------------------------------------------- /src/modules1_exe/src/main.rs: -------------------------------------------------------------------------------- 1 | use modules1::*; 2 | 3 | fn user_accepted(role: &Role) { 4 | println!("You are logged in as a {role:?}"); 5 | } 6 | 7 | fn main() { 8 | let test = User::new("test", "test", LoginAction::Accept(Role::Admin)); 9 | 10 | //build_users_file(); 11 | let users = get_users(); 12 | 13 | println!("Welcome to the (Not Very) Secure Server"); 14 | println!("Enter your username:"); 15 | let mut username = String::new(); 16 | let mut password = String::new(); 17 | let stdin = std::io::stdin(); 18 | stdin.read_line(&mut username).unwrap(); 19 | println!("Enter your password:"); 20 | stdin.read_line(&mut password).unwrap(); 21 | 22 | match login(&users, &username, &password) { 23 | None => { 24 | println!("{} is not a known user.", username.trim()); 25 | println!("This is where we handle new users."); 26 | } 27 | Some(login_action) => { 28 | login_action.do_login( 29 | user_accepted, 30 | |reason| { 31 | println!("Access denied"); 32 | println!("{reason:?}"); 33 | } 34 | ) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/modules2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "modules2" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | serde = { version = "1.0.152", features = [ "derive" ] } 10 | serde_json = "1.0.92" 11 | sha2 = "0" 12 | -------------------------------------------------------------------------------- /src/modules2/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod user; 2 | mod login_action; 3 | use std::collections::HashMap; 4 | pub use user::User; 5 | pub use login_action::*; 6 | 7 | pub mod serde { 8 | pub use serde::*; 9 | } 10 | 11 | pub fn hash_password(password: &str) -> String { 12 | use sha2::Digest; 13 | let mut hasher = sha2::Sha256::new(); 14 | hasher.update(password); 15 | format!("{:X}", hasher.finalize()) 16 | } 17 | 18 | pub fn build_users_file() { 19 | use std::io::Write; 20 | 21 | let users = get_users_old(); 22 | //let json = serde_json::to_string(&users).unwrap(); 23 | let json = serde_json::to_string_pretty(&users).unwrap(); 24 | let mut f = std::fs::File::create("users.json").unwrap(); 25 | f.write_all(json.as_bytes()).unwrap(); 26 | } 27 | 28 | pub fn save_users_file(users: &HashMap) { 29 | use std::io::Write; 30 | let json = serde_json::to_string_pretty(&users).unwrap(); 31 | let mut f = std::fs::File::create("users.json").unwrap(); 32 | f.write_all(json.as_bytes()).unwrap(); 33 | } 34 | 35 | #[allow(dead_code)] 36 | fn get_users_old() -> HashMap { 37 | /*let mut result = HashMap::new(); 38 | result.insert("herbert".to_string(), User::new("herbert", "password", LoginAction::Accept(Role::Admin))); 39 | result*/ 40 | let mut users = vec![ 41 | User::new("herbert", "password", LoginAction::Accept(Role::Admin)), 42 | User::new("bob", "password", LoginAction::Accept(Role::User)), 43 | User::new("fred", "password", LoginAction::Denied(DeniedReason::PasswordExpired)), 44 | ]; 45 | /*users 46 | .iter() // Create an iterator 47 | .map(|user| (user.username.clone(), user.clone()) ) 48 | .collect()*/ 49 | users 50 | .drain(0..) 51 | .map(|user| ( user.username.clone(), user )) 52 | .collect() 53 | } 54 | 55 | pub fn get_users() -> HashMap { 56 | let json = std::fs::read_to_string("users.json").unwrap(); 57 | serde_json::from_str(&json).unwrap() 58 | } 59 | 60 | pub fn login(users: &HashMap, username: &str, password: &str) -> Option { 61 | let username = username.trim().to_lowercase(); 62 | let password = hash_password(password.trim()); 63 | 64 | users 65 | .get(&username) 66 | .filter(|user| user.password == password) 67 | .map(|user| user.action.clone()) 68 | } 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/modules2/src/login_action/action.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize}; 2 | use crate::{Role, DeniedReason}; 3 | 4 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 5 | pub enum LoginAction { 6 | Accept(Role), 7 | Denied(DeniedReason), 8 | } 9 | 10 | impl LoginAction { 11 | pub fn do_login(&self, on_success: fn(&Role), on_denied: fn(&DeniedReason)) { 12 | match self { 13 | Self::Accept(role) => on_success(role), 14 | Self::Denied(reason) => on_denied(reason), 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/modules2/src/login_action/denied_reason.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize}; 2 | 3 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 4 | pub enum DeniedReason { 5 | PasswordExpired, 6 | AccountLocked{reason: String}, 7 | } -------------------------------------------------------------------------------- /src/modules2/src/login_action/mod.rs: -------------------------------------------------------------------------------- 1 | mod denied_reason; 2 | mod action; 3 | mod role; 4 | 5 | pub use denied_reason::DeniedReason; 6 | pub use action::LoginAction; 7 | pub use role::Role; 8 | 9 | -------------------------------------------------------------------------------- /src/modules2/src/login_action/role.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize}; 2 | 3 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 4 | pub enum Role { 5 | Admin, 6 | User, 7 | Limited 8 | } -------------------------------------------------------------------------------- /src/modules2/src/user.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize}; 2 | use crate::{LoginAction, hash_password}; 3 | 4 | #[derive(Clone, Debug, Serialize, Deserialize)] 5 | pub struct User { 6 | pub username: String, 7 | pub(crate) password: String, 8 | pub action: LoginAction, 9 | } 10 | 11 | impl User { 12 | pub fn new(username: &str, password: &str, action: LoginAction) -> Self { 13 | Self { 14 | username: username.to_string(), 15 | password: hash_password(password), 16 | action 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/rayon_threads/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rayon_threads" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rayon = "1.6.1" 10 | -------------------------------------------------------------------------------- /src/rayon_threads/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{thread::sleep, time::Duration}; 2 | 3 | fn hello(n: u64) { 4 | println!("Hello from thread {n}"); 5 | sleep(Duration::from_secs(n)); 6 | println!("Bye from thread {n}"); 7 | } 8 | 9 | fn main() { 10 | rayon::scope(|s| { 11 | for i in 0..8 { 12 | let my_i = i; 13 | s.spawn(move |_| hello(my_i)); 14 | } 15 | }); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/rc_bench/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rc_bench" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/rc_bench/src/atomic_rc_cat.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::fmt::Display; 3 | use std::sync::Arc; 4 | 5 | struct Cat { 6 | name: String, 7 | status: RefCell, 8 | } 9 | 10 | impl Display for Cat { 11 | /// Print service for cats 12 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 13 | write!(f, "{} ({})", self.name, self.status.borrow()) 14 | } 15 | } 16 | 17 | struct CatOwner { 18 | cat: Arc, 19 | } 20 | 21 | impl CatOwner { 22 | fn feed_cat(&self) { 23 | let mut borrow = self.cat.status.borrow_mut(); 24 | *borrow = "Purring".to_string(); 25 | } 26 | } 27 | 28 | pub fn feed_cats(n_cats: usize) { 29 | let mut owners = Vec::new(); 30 | for i in 0 .. n_cats { 31 | // Make a cat 32 | let new_cat = Arc::new(Cat{ 33 | name: format!("Fuzzy Friend {}", i+1), 34 | status: RefCell::new(String::new()), 35 | }); 36 | 37 | // Associate the owner with the ID 38 | owners.push( 39 | CatOwner { cat: new_cat.clone() } 40 | ); 41 | } 42 | 43 | // Start the timer 44 | let now = std::time::Instant::now(); 45 | owners 46 | .iter() 47 | .for_each(|owner| owner.feed_cat()); 48 | let duration = now.elapsed(); 49 | 50 | super::print_result("ARC Cats", duration); 51 | } -------------------------------------------------------------------------------- /src/rc_bench/src/cat_store.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use std::collections::HashMap; 3 | 4 | struct Cat { 5 | name: String, 6 | status: String, 7 | } 8 | 9 | impl Display for Cat { 10 | /// Print service for cats 11 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 12 | write!(f, "{} ({})", self.name, self.status) 13 | } 14 | } 15 | 16 | struct CatOwner { 17 | cat_id: usize, 18 | } 19 | 20 | /// Provides a central storage location for cats 21 | struct CatStore { 22 | next_cat: usize, 23 | cats: HashMap, 24 | } 25 | 26 | impl CatStore { 27 | /// Creates a new CatStore 28 | fn new() -> Self { 29 | Self { 30 | next_cat: 0, 31 | cats: HashMap::new(), 32 | } 33 | } 34 | 35 | /// Add a new cat, and return its ID number 36 | fn add_cat(&mut self, cat: Cat) -> usize { 37 | let id = self.next_cat; 38 | self.next_cat += 1; 39 | 40 | self.cats.insert(id, cat); 41 | id 42 | } 43 | 44 | /// Find cat by id, set status to "purring" 45 | fn feed_cat(&mut self, id: usize) { 46 | if let Some(mut cat) = self.cats.get_mut(&id) { 47 | cat.status = "Purring".to_string(); 48 | } 49 | } 50 | } 51 | 52 | pub fn feed_cats_by_id(n_cats: usize) { 53 | let mut store = CatStore::new(); 54 | let mut owners = Vec::new(); 55 | for i in 0 .. n_cats { 56 | // Make a cat 57 | let new_cat = Cat{ 58 | name: format!("Fuzzy Friend {}", i+1), 59 | status: String::new(), 60 | }; 61 | // Add it to the central cat store and get ID 62 | let new_id = store.add_cat(new_cat); 63 | 64 | // Associate the owner with the ID 65 | owners.push( 66 | CatOwner { cat_id: new_id } 67 | ); 68 | } 69 | 70 | // Start the timer 71 | let now = std::time::Instant::now(); 72 | owners 73 | .iter() 74 | .for_each(|owner| store.feed_cat(owner.cat_id)); 75 | let duration = now.elapsed(); 76 | 77 | super::print_result("Cat Store", duration); 78 | } -------------------------------------------------------------------------------- /src/rc_bench/src/cat_vec.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | struct Cat { 4 | name: String, 5 | status: String, 6 | } 7 | 8 | impl Display for Cat { 9 | /// Print service for cats 10 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 11 | write!(f, "{} ({})", self.name, self.status) 12 | } 13 | } 14 | 15 | struct CatOwner { 16 | cat_idx: usize, 17 | } 18 | 19 | /// Provides a central storage location for cats 20 | struct CatStore { 21 | cats: Vec, 22 | } 23 | 24 | impl CatStore { 25 | /// Creates a new CatStore 26 | fn new() -> Self { 27 | Self { 28 | cats: Vec::new(), 29 | } 30 | } 31 | 32 | /// Add a new cat, and return its ID number 33 | fn add_cat(&mut self, cat: Cat) -> usize { 34 | let id = self.cats.len(); 35 | self.cats.push(cat); 36 | id 37 | } 38 | 39 | /// Find cat by id, set status to "purring" 40 | fn feed_cat(&mut self, id: usize) { 41 | self.cats[id].status = "Purring".to_string(); 42 | } 43 | } 44 | 45 | pub fn feed_cats_by_id(n_cats: usize) { 46 | let mut store = CatStore::new(); 47 | let mut owners = Vec::new(); 48 | for i in 0 .. n_cats { 49 | // Make a cat 50 | let new_cat = Cat{ 51 | name: format!("Fuzzy Friend {}", i+1), 52 | status: String::new(), 53 | }; 54 | // Add it to the central cat store and get ID 55 | let new_id = store.add_cat(new_cat); 56 | 57 | // Associate the owner with the ID 58 | owners.push( 59 | CatOwner { cat_idx: new_id } 60 | ); 61 | } 62 | 63 | // Start the timer 64 | let now = std::time::Instant::now(); 65 | owners 66 | .iter() 67 | .for_each(|owner| store.feed_cat(owner.cat_idx)); 68 | let duration = now.elapsed(); 69 | 70 | super::print_result("Vector of Cats", duration); 71 | } -------------------------------------------------------------------------------- /src/rc_bench/src/main.rs: -------------------------------------------------------------------------------- 1 | // Store cats in a simple vector and index by vector position. 2 | // VERY fast, has the downside that you can't insert or delete cats 3 | // without ruining every index 4 | mod cat_vec; 5 | 6 | // Store cats in a HashMap, giving each cat a stable ID number. 7 | // Slower. 8 | mod cat_store; 9 | 10 | // Store each cat as a reference counted pointer, and hand a 11 | // reference to the cat to each owner. Simpler, there's no 12 | // cat store at all. 13 | mod rc_cat; 14 | 15 | // Reference counted cats, but using thread-safe reference 16 | // counting. 17 | mod atomic_rc_cat; 18 | 19 | const NUMBER_OF_CATS: usize = 10_000_000; 20 | 21 | fn print_result(method: &str, time: std::time::Duration) { 22 | let usecs = format!("{} μsecs", time.as_micros()); 23 | let nanos_per_cat = format!("{} nanos per cat", time.as_nanos() as usize / NUMBER_OF_CATS); 24 | println!("{method:<30}{usecs:<20}{nanos_per_cat:<20}"); 25 | } 26 | 27 | fn main() { 28 | cat_vec::feed_cats_by_id(NUMBER_OF_CATS); 29 | rc_cat::feed_cats(NUMBER_OF_CATS); 30 | atomic_rc_cat::feed_cats(NUMBER_OF_CATS); 31 | cat_store::feed_cats_by_id(NUMBER_OF_CATS); 32 | } 33 | -------------------------------------------------------------------------------- /src/rc_bench/src/rc_cat.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::fmt::Display; 3 | use std::rc::Rc; 4 | 5 | struct Cat { 6 | name: String, 7 | status: RefCell, 8 | } 9 | 10 | impl Display for Cat { 11 | /// Print service for cats 12 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 13 | write!(f, "{} ({})", self.name, self.status.borrow()) 14 | } 15 | } 16 | 17 | struct CatOwner { 18 | cat: Rc, 19 | } 20 | 21 | impl CatOwner { 22 | fn feed_cat(&self) { 23 | let mut borrow = self.cat.status.borrow_mut(); 24 | *borrow = "Purring".to_string(); 25 | } 26 | } 27 | 28 | pub fn feed_cats(n_cats: usize) { 29 | let mut owners = Vec::new(); 30 | for i in 0 .. n_cats { 31 | // Make a cat 32 | let new_cat = Rc::new(Cat{ 33 | name: format!("Fuzzy Friend {}", i+1), 34 | status: RefCell::new(String::new()), 35 | }); 36 | 37 | // Associate the owner with the ID 38 | owners.push( 39 | CatOwner { cat: new_cat.clone() } 40 | ); 41 | } 42 | 43 | // Start the timer 44 | let now = std::time::Instant::now(); 45 | owners 46 | .iter() 47 | .for_each(|owner| owner.feed_cat()); 48 | let duration = now.elapsed(); 49 | 50 | super::print_result("RC Cats", duration); 51 | } -------------------------------------------------------------------------------- /src/rocket/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rocket" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rocket = "0.5.0-rc.2" 10 | -------------------------------------------------------------------------------- /src/rocket/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate rocket; 2 | 3 | #[get("/")] 4 | fn index() -> &'static str { 5 | "Hello, world!" 6 | } 7 | 8 | #[launch] 9 | fn rocket() -> _ { 10 | rocket::build().mount("/", routes![index]) 11 | } -------------------------------------------------------------------------------- /src/rocket2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rocket2" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rocket = { version = "0.5.0-rc.2", features = [ "json", "msgpack", "uuid" ] } 10 | bincode = "1" 11 | auth_json = { path = "../auth_json" } -------------------------------------------------------------------------------- /src/rocket2/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Please Login 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 34 | 35 | -------------------------------------------------------------------------------- /src/rocket2/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate rocket; 2 | use rocket::fs::NamedFile; 3 | use rocket::serde::{json::Json, Deserialize, Serialize}; 4 | 5 | #[get("/")] 6 | pub async fn login_page<'a>() -> NamedFile { 7 | NamedFile::open("login.html").await.unwrap() 8 | } 9 | 10 | #[derive(Serialize, Deserialize, Clone, Debug)] 11 | #[serde(crate = "rocket::serde")] 12 | pub struct Login { 13 | username: String, 14 | password: String, 15 | } 16 | 17 | #[post("/api/login", data = "")] 18 | pub async fn login(user: Json) { 19 | use rocket::tokio::io::{AsyncWriteExt, AsyncReadExt}; 20 | use rocket::tokio::net::TcpStream; 21 | 22 | use auth_json::*; 23 | let login_attempt = user.0; 24 | 25 | let mut stream = TcpStream::connect("127.0.0.1:8123").await.unwrap(); 26 | let message = bincode::serialize(&login_attempt).unwrap(); 27 | stream.write_all(&message).await.unwrap(); 28 | 29 | let mut buf = vec![0; 1024]; 30 | let n = stream.read(&mut buf).await.unwrap(); 31 | let response: Option = bincode::deserialize(&buf[0..n]).unwrap(); 32 | 33 | println!("{response:?}"); 34 | } 35 | 36 | #[launch] 37 | fn rocket() -> _ { 38 | rocket::build().mount("/", routes![login_page, login]) 39 | } -------------------------------------------------------------------------------- /src/tcp_login_server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tcp_login_server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.69" 10 | serde = { version = "1.0.152", features = ["derive"] } 11 | serde_json = "1.0.93" 12 | tokio = { version = "1.25.0", features = ["full"] } 13 | bincode = "1" 14 | auth_json = { path = "../auth_json" } 15 | once_cell = "1" 16 | parking_lot = "0" 17 | -------------------------------------------------------------------------------- /src/tcp_login_server/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use once_cell::sync::Lazy; 3 | use parking_lot::RwLock; 4 | use serde::{Serialize, Deserialize}; 5 | use tokio::{net::{TcpListener, TcpStream}, spawn, io::{AsyncReadExt, AsyncWriteExt}}; 6 | use auth_json::*; 7 | 8 | static USERS: Lazy>> = Lazy::new(|| RwLock::new(get_users())); 9 | 10 | #[derive(Serialize, Deserialize)] 11 | struct LoginRequest { 12 | username: String, 13 | password: String, 14 | } 15 | 16 | async fn rpc_server() -> anyhow::Result<()> { 17 | let listener = TcpListener::bind("127.0.0.1:8123").await?; 18 | 19 | loop { 20 | let (mut socket, address) = listener.accept().await?; 21 | spawn(async move { 22 | let mut buf = vec![0; 1024]; 23 | loop { 24 | let n = socket 25 | .read(&mut buf) 26 | .await 27 | .expect("failed to read data from socket"); 28 | 29 | if n == 0 { 30 | return; 31 | } 32 | 33 | let mut response = None; 34 | if let Ok(request) = bincode::deserialize::(&buf[0..n]) { 35 | response = login(&USERS.read(), &request.username, &request.password); 36 | 37 | } 38 | 39 | let bytes = bincode::serialize(&response).unwrap(); 40 | socket 41 | .write_all(&bytes) 42 | .await 43 | .expect("failed to write data to socket"); 44 | } 45 | }); 46 | } 47 | Ok(()) 48 | } 49 | 50 | async fn rpc_client() -> anyhow::Result<()> { 51 | println!("Welcome to the (Not Very) Secure Server"); 52 | println!("Enter your username:"); 53 | let mut username = String::new(); 54 | let mut password = String::new(); 55 | let stdin = std::io::stdin(); 56 | stdin.read_line(&mut username).unwrap(); 57 | println!("Enter your password:"); 58 | stdin.read_line(&mut password).unwrap(); 59 | 60 | let login_attempt = LoginRequest { 61 | username, password 62 | }; 63 | 64 | 65 | let mut stream = TcpStream::connect("127.0.0.1:8123").await?; 66 | let message = bincode::serialize(&login_attempt)?; 67 | stream.write_all(&message).await?; 68 | 69 | let mut buf = vec![0; 1024]; 70 | let n = stream.read(&mut buf).await?; 71 | let response: Option = bincode::deserialize(&buf[0..n])?; 72 | 73 | 74 | match response { 75 | None => { 76 | println!("{} is not a known user.", login_attempt.username.trim()); 77 | println!("This is where we handle new users."); 78 | } 79 | Some(login_action) => { 80 | login_action.do_login( 81 | |user| println!("Welcome {user:?}"), 82 | |reason| { 83 | println!("Access denied"); 84 | println!("{reason:?}"); 85 | } 86 | ) 87 | } 88 | } 89 | 90 | Ok(()) 91 | } 92 | 93 | #[tokio::main] 94 | async fn main() -> anyhow::Result<()> { 95 | let args: Vec = std::env::args().collect(); 96 | if args.len() != 2 { 97 | println!("You must run with either --server or --client"); 98 | } else { 99 | match args[1].as_str() { 100 | "--server" => rpc_server().await?, 101 | "--client" => rpc_client().await?, 102 | _ => println!("You must run with either --server or --client"), 103 | } 104 | } 105 | Ok(()) 106 | } 107 | -------------------------------------------------------------------------------- /src/tcp_login_server/users.json: -------------------------------------------------------------------------------- 1 | { 2 | "bob": { 3 | "username": "bob", 4 | "password": "password", 5 | "action": { 6 | "Accept": "User" 7 | } 8 | }, 9 | "herbert": { 10 | "username": "herbert", 11 | "password": "password", 12 | "action": { 13 | "Accept": "Admin" 14 | } 15 | }, 16 | "toby": { 17 | "username": "toby", 18 | "password": "password", 19 | "action": { 20 | "Denied": "PasswordExpired" 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/tcp_login_server_bench/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tcp_login_server_bench" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.69" 10 | serde = { version = "1.0.152", features = ["derive"] } 11 | serde_json = "1.0.93" 12 | tokio = { version = "1.25.0", features = ["full"] } 13 | bincode = "1" 14 | auth_json = { path = "../auth_json" } 15 | once_cell = "1" 16 | parking_lot = "0" 17 | -------------------------------------------------------------------------------- /src/thread_channels/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thread_channels" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/thread_channels/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::mpsc, thread}; 2 | 3 | fn main() { 4 | let (tx, rx) = mpsc::channel::(); 5 | 6 | let handle = thread::spawn(move || { 7 | loop { 8 | let n = rx.recv().unwrap(); 9 | match n { 10 | 1 => println!("Hi from worker thread"), 11 | _ => break, 12 | } 13 | } 14 | println!("Thread closing cleanly"); 15 | }); 16 | 17 | for _ in 0..10 { 18 | tx.send(1).unwrap(); 19 | } 20 | tx.send(0).unwrap(); 21 | 22 | handle.join().unwrap(); 23 | } 24 | -------------------------------------------------------------------------------- /src/tokio_channels/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tokio_channels" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.69" 10 | serde = { version = "1.0.152", features = ["derive"] } 11 | serde_json = "1.0.93" 12 | tokio = { version = "1.25.0", features = ["full"] } 13 | -------------------------------------------------------------------------------- /src/tokio_channels/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use serde::{Serialize, Deserialize}; 4 | use tokio::{net::{TcpListener, TcpStream}, spawn, io::{AsyncReadExt, AsyncWriteExt}, sync::mpsc::{self, Receiver}, time::sleep}; 5 | 6 | #[derive(Serialize, Deserialize)] 7 | enum Request { 8 | Ping, 9 | } 10 | 11 | #[derive(Serialize, Deserialize)] 12 | enum Response { 13 | Error, 14 | Ack, 15 | } 16 | 17 | async fn rpc_server() -> anyhow::Result<()> { 18 | let listener = TcpListener::bind("127.0.0.1:8123").await?; 19 | 20 | loop { 21 | let (mut socket, address) = listener.accept().await?; 22 | spawn(async move { 23 | let mut buf = vec![0; 1024]; 24 | loop { 25 | let n = socket 26 | .read(&mut buf) 27 | .await 28 | .expect("failed to read data from socket"); 29 | 30 | if n == 0 { 31 | return; 32 | } 33 | 34 | let mut response = Response::Error; 35 | let request = serde_json::from_slice(&buf[0..n]); 36 | match request { 37 | Err(..) => return, 38 | Ok(request) => { 39 | match request { 40 | Request::Ping => response = Response::Ack, 41 | } 42 | } 43 | } 44 | 45 | let bytes = serde_json::to_vec(&response).unwrap(); 46 | socket 47 | .write_all(&bytes) 48 | .await 49 | .expect("failed to write data to socket"); 50 | } 51 | }); 52 | } 53 | Ok(()) 54 | } 55 | 56 | async fn rpc_client(mut rx: Receiver) -> anyhow::Result<()> { 57 | let mut stream = TcpStream::connect("127.0.0.1:8123").await?; 58 | 59 | while let Some(n) = rx.recv().await { 60 | let message = serde_json::to_vec(&Request::Ping)?; 61 | stream.write_all(&message).await?; 62 | 63 | let mut buf = vec![0; 1024]; 64 | let n = stream.read(&mut buf).await?; 65 | let response: Response = serde_json::from_slice(&buf[0..n])?; 66 | match response { 67 | Response::Error => println!("Error!"), 68 | Response::Ack => println!("Ack"), 69 | } 70 | } 71 | 72 | Ok(()) 73 | } 74 | 75 | #[tokio::main] 76 | async fn main() -> anyhow::Result<()> { 77 | // Create a channel 78 | let (tx, mut rx) = mpsc::channel(32); // 32 = buffer size 79 | 80 | spawn(rpc_server()); 81 | spawn(rpc_client(rx)); 82 | 83 | for _ in 0..10 { 84 | sleep(Duration::from_secs(1)).await; 85 | let _ = tx.send(1).await; 86 | } 87 | 88 | Ok(()) 89 | } 90 | -------------------------------------------------------------------------------- /src/tokio_channels2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tokio_channels2" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.69" 10 | serde = { version = "1.0.152", features = ["derive"] } 11 | serde_json = "1.0.93" 12 | tokio = { version = "1.25.0", features = ["full"] } 13 | -------------------------------------------------------------------------------- /src/tokio_channels2/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use serde::{Serialize, Deserialize}; 4 | use tokio::{net::{TcpListener, TcpStream}, spawn, io::{AsyncReadExt, AsyncWriteExt}, sync::mpsc::{self, Receiver}, time::sleep}; 5 | 6 | #[derive(Serialize, Deserialize)] 7 | enum Request { 8 | Ping, 9 | } 10 | 11 | #[derive(Serialize, Deserialize)] 12 | enum Response { 13 | Error, 14 | Ack, 15 | } 16 | 17 | async fn rpc_server() -> anyhow::Result<()> { 18 | let listener = TcpListener::bind("127.0.0.1:8123").await?; 19 | 20 | loop { 21 | let (mut socket, address) = listener.accept().await?; 22 | spawn(async move { 23 | let mut buf = vec![0; 1024]; 24 | loop { 25 | let n = socket 26 | .read(&mut buf) 27 | .await 28 | .expect("failed to read data from socket"); 29 | 30 | if n == 0 { 31 | return; 32 | } 33 | 34 | let mut response = Response::Error; 35 | let request = serde_json::from_slice(&buf[0..n]); 36 | match request { 37 | Err(..) => return, 38 | Ok(request) => { 39 | match request { 40 | Request::Ping => response = Response::Ack, 41 | } 42 | } 43 | } 44 | 45 | let bytes = serde_json::to_vec(&response).unwrap(); 46 | socket 47 | .write_all(&bytes) 48 | .await 49 | .expect("failed to write data to socket"); 50 | } 51 | }); 52 | } 53 | Ok(()) 54 | } 55 | 56 | async fn rpc_client(mut rx: tokio::sync::broadcast::Receiver) -> anyhow::Result<()> { 57 | let mut stream = TcpStream::connect("127.0.0.1:8123").await?; 58 | 59 | loop { 60 | let _n = rx.recv().await?; 61 | let message = serde_json::to_vec(&Request::Ping)?; 62 | stream.write_all(&message).await?; 63 | 64 | let mut buf = vec![0; 1024]; 65 | let n = stream.read(&mut buf).await?; 66 | let response: Response = serde_json::from_slice(&buf[0..n])?; 67 | match response { 68 | Response::Error => println!("Error!"), 69 | Response::Ack => println!("Ack"), 70 | } 71 | } 72 | 73 | Ok(()) 74 | } 75 | 76 | #[tokio::main] 77 | async fn main() -> anyhow::Result<()> { 78 | // Create a channel 79 | let (tx, _rx) = tokio::sync::broadcast::channel::(32); 80 | spawn(rpc_server()); 81 | for _ in 0..10 { 82 | spawn(rpc_client(tx.subscribe())); 83 | } 84 | 85 | for _ in 0..10 { 86 | sleep(Duration::from_secs(1)).await; 87 | let _ = tx.send(1); 88 | } 89 | 90 | Ok(()) 91 | } 92 | -------------------------------------------------------------------------------- /src/tokio_rpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tokio_rpc" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.69" 10 | serde = { version = "1.0.152", features = ["derive"] } 11 | serde_json = "1.0.93" 12 | tokio = { version = "1.25.0", features = ["full"] } 13 | -------------------------------------------------------------------------------- /src/tokio_rpc/src/main.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize}; 2 | use tokio::{net::{TcpListener, TcpStream}, spawn, io::{AsyncReadExt, AsyncWriteExt}}; 3 | 4 | #[derive(Serialize, Deserialize)] 5 | enum Request { 6 | Ping, 7 | } 8 | 9 | #[derive(Serialize, Deserialize)] 10 | enum Response { 11 | Error, 12 | Ack, 13 | } 14 | 15 | async fn rpc_server() -> anyhow::Result<()> { 16 | let listener = TcpListener::bind("127.0.0.1:8123").await?; 17 | 18 | loop { 19 | let (mut socket, address) = listener.accept().await?; 20 | spawn(async move { 21 | let mut buf = vec![0; 1024]; 22 | loop { 23 | let n = socket 24 | .read(&mut buf) 25 | .await 26 | .expect("failed to read data from socket"); 27 | 28 | if n == 0 { 29 | return; 30 | } 31 | 32 | let mut response = Response::Error; 33 | let request = serde_json::from_slice(&buf[0..n]); 34 | match request { 35 | Err(..) => return, 36 | Ok(request) => { 37 | match request { 38 | Request::Ping => response = Response::Ack, 39 | } 40 | } 41 | } 42 | 43 | let bytes = serde_json::to_vec(&response).unwrap(); 44 | socket 45 | .write_all(&bytes) 46 | .await 47 | .expect("failed to write data to socket"); 48 | } 49 | }); 50 | } 51 | Ok(()) 52 | } 53 | 54 | async fn rpc_client() -> anyhow::Result<()> { 55 | let mut stream = TcpStream::connect("127.0.0.1:8123").await?; 56 | let message = serde_json::to_vec(&Request::Ping)?; 57 | stream.write_all(&message).await?; 58 | 59 | let mut buf = vec![0; 1024]; 60 | let n = stream.read(&mut buf).await?; 61 | let response: Response = serde_json::from_slice(&buf[0..n])?; 62 | match response { 63 | Response::Error => println!("Error!"), 64 | Response::Ack => println!("Ack"), 65 | } 66 | 67 | Ok(()) 68 | } 69 | 70 | #[tokio::main] 71 | async fn main() -> anyhow::Result<()> { 72 | let args: Vec = std::env::args().collect(); 73 | if args.len() != 2 { 74 | println!("You must run with either --server or --client"); 75 | } else { 76 | match args[1].as_str() { 77 | "--server" => rpc_server().await?, 78 | "--client" => rpc_client().await?, 79 | _ => println!("You must run with either --server or --client"), 80 | } 81 | } 82 | Ok(()) 83 | } 84 | -------------------------------------------------------------------------------- /src/tokio_tcp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tokio_tcp" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.69" 10 | tokio = { version = "1.25.0", features = ["full"] } 11 | -------------------------------------------------------------------------------- /src/tokio_tcp/src/main.rs: -------------------------------------------------------------------------------- 1 | use tokio::{net::TcpListener, spawn, io::{AsyncReadExt, AsyncWriteExt}}; 2 | 3 | #[tokio::main] 4 | async fn main() -> anyhow::Result<()> { 5 | let listener = TcpListener::bind("127.0.0.1:8123").await?; 6 | 7 | loop { 8 | let (mut socket, address) = listener.accept().await?; 9 | spawn(async move { 10 | let mut buf = vec![0; 1024]; 11 | loop { 12 | let n = socket 13 | .read(&mut buf) 14 | .await 15 | .expect("failed to read data from socket"); 16 | 17 | if n == 0 { 18 | return; 19 | } 20 | 21 | socket 22 | .write_all(&buf[0..n]) 23 | .await 24 | .expect("failed to write data to socket"); 25 | } 26 | }); 27 | } 28 | Ok(()) 29 | } 30 | -------------------------------------------------------------------------------- /src/userman/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "userman" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | auth_userman = { path = "../auth_userman" } 10 | clap = { version = "4", features = ["derive"] } 11 | colored = "2.0.0" 12 | -------------------------------------------------------------------------------- /src/userman/src/main.rs: -------------------------------------------------------------------------------- 1 | use auth_userman::*; 2 | use clap::{Parser, Subcommand}; 3 | use std::collections::HashMap; 4 | 5 | type UserMap = HashMap; 6 | 7 | #[derive(Parser)] 8 | #[command()] 9 | struct Args { 10 | #[command(subcommand)] 11 | command: Option, 12 | } 13 | 14 | #[derive(Subcommand)] 15 | enum Commands { 16 | /// List all users. 17 | List, 18 | /// Add a user. 19 | Add { 20 | /// Username 21 | #[arg(long)] 22 | username: String, 23 | 24 | /// Password 25 | #[arg(long)] 26 | password: String, 27 | 28 | /// Optional - mark as a limited user 29 | #[arg(long)] 30 | limited: Option, 31 | 32 | /// Optional - mark as an admin 33 | #[arg(long)] 34 | admin: Option, 35 | }, 36 | /// Delete a user 37 | Delete { 38 | /// Username 39 | username: String, 40 | }, 41 | /// Change a password 42 | ChangePassword { 43 | /// Username 44 | username: String, 45 | 46 | /// New Password 47 | new_password: String, 48 | } 49 | } 50 | 51 | fn list_users(users: &UserMap) { 52 | use colored::Colorize; 53 | println!("{:<20}{:<20}", "Username", "Login Action"); 54 | println!("{:-<40}", ""); 55 | 56 | users.iter().for_each(|(_, user)| { 57 | let action = format!("{:?}", user.action); 58 | let action = match user.action { 59 | LoginAction::Accept(..) => action.green(), 60 | LoginAction::Denied(..) => action.red(), 61 | }; 62 | println!("{:<20}{:<20}", user.username, action); 63 | }); 64 | } 65 | 66 | fn add_user( 67 | users: &mut UserMap, 68 | username: String, 69 | password: String, 70 | limited: Option, 71 | admin: Option, 72 | ) { 73 | if users.contains_key(&username) { 74 | println!("{username} already exists, aborting."); 75 | return; 76 | } 77 | let action = LoginAction::Accept(if limited.is_some() { 78 | Role::Limited 79 | } else if admin.is_some() { 80 | Role::Admin 81 | } else { 82 | Role::User 83 | }); 84 | let user = User::new(&username, &password, action); 85 | users.insert(username, user); 86 | save_users_file(users); 87 | } 88 | 89 | fn delete_user(users: &mut UserMap, username: String) { 90 | if !users.contains_key(&username) { 91 | println!("{username} does not exist, aborting"); 92 | return; 93 | } 94 | users.remove(&username); 95 | save_users_file(users); 96 | } 97 | 98 | fn change_password(users: &mut UserMap, username: String, new_password: String) { 99 | if let Some(mut user) = users.get_mut(&username) { 100 | user.password = hash_password(&new_password); 101 | save_users_file(users); 102 | } else { 103 | println!("{username} does not exist, aborting"); 104 | } 105 | } 106 | 107 | fn main() { 108 | let mut users = get_users(); 109 | let cli = Args::parse(); 110 | match cli.command { 111 | Some(Commands::List) => { 112 | list_users(&users); 113 | } 114 | Some(Commands::Add { 115 | username, 116 | password, 117 | limited, 118 | admin, 119 | }) => { 120 | add_user(&mut users, username, password, limited, admin); 121 | } 122 | Some(Commands::ChangePassword { username, new_password }) => { 123 | change_password(&mut users, username, new_password); 124 | } 125 | Some(Commands::Delete { username }) => { 126 | delete_user(&mut users, username); 127 | } 128 | None => { 129 | println!("Run with --help to see instructions"); 130 | std::process::exit(0); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/userman/users.json: -------------------------------------------------------------------------------- 1 | { 2 | "herbert": { 3 | "username": "herbert", 4 | "password": "5E884898DA28047151D0E56F8DC6292773603D0D6AABBDD62A11EF721D1542D8", 5 | "action": { 6 | "Accept": "Admin" 7 | } 8 | }, 9 | "fred": { 10 | "username": "fred", 11 | "password": "5E884898DA28047151D0E56F8DC6292773603D0D6AABBDD62A11EF721D1542D8", 12 | "action": { 13 | "Denied": "PasswordExpired" 14 | } 15 | }, 16 | "bob": { 17 | "username": "bob", 18 | "password": "5E884898DA28047151D0E56F8DC6292773603D0D6AABBDD62A11EF721D1542D8", 19 | "action": { 20 | "Accept": "User" 21 | } 22 | } 23 | } --------------------------------------------------------------------------------