├── .github └── workflows │ ├── build-slides.yaml │ └── build-test-hol.yaml ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── 100-foundations │ ├── .gitignore │ ├── .vscode │ │ ├── 0100-fundamentals-rust.code-snippets │ │ ├── 0200-fundamentals-loops-rust.code-snippets │ │ ├── 0300-fundamentals-arrays-tuples-rust.code-snippets │ │ ├── 0400-structs-rust.code-snippets │ │ ├── 0500-ownership-rust.code-snippets │ │ ├── 0510-memory-management.code-snippets │ │ ├── 0600-structs-lifetimes.code-snippets │ │ ├── 0700-enums-rust.code-snippets │ │ ├── 0800-closures.code-snippets │ │ ├── 0900-threading.code-snippets │ │ ├── extensions.json │ │ └── settings.json │ ├── Cargo.lock │ ├── Cargo.toml │ ├── backups │ │ ├── 01_variables_control_flow.rs │ │ ├── 02_loops_main.rs │ │ ├── 03_tuples_main.rs │ │ ├── 04_arrays_main.rs │ │ ├── 05_vec_main.rs │ │ ├── 06_structs_main.rs │ │ ├── 07_ownership.rs │ │ ├── 08_lifetimes.rs │ │ ├── 09_lifetimes_structs_main.rs │ │ ├── 10_enums_iterators_enumerators.rs │ │ ├── 11_error_handling.rs │ │ ├── 12_iterators.rs │ │ ├── 13_into_iterator.rs │ │ ├── 14_threading.rs │ │ ├── 15_generics_boxing.rs │ │ ├── 16_more_traits.rs │ │ ├── 17_trait_objects.rs │ │ ├── 18_fibonacci.rs │ │ ├── 19-reverse.rs │ │ └── 20-async.rs │ ├── readme.md │ ├── src │ │ └── main.rs │ ├── stairway.md │ └── username.txt ├── 110-wasm │ ├── .gitignore │ ├── .vscode │ │ └── settings.json │ ├── Cargo.toml │ ├── justfile │ ├── node │ │ ├── app.ts │ │ ├── package.json │ │ └── tsconfig.json │ ├── readme.md │ ├── src │ │ └── lib.rs │ ├── tests │ │ └── web.rs │ └── www │ │ ├── package.json │ │ ├── src │ │ ├── bootstrap.ts │ │ ├── index.css │ │ ├── index.html │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── webpack.config.js ├── 111-turmrechnen │ ├── .gitignore │ ├── .vscode │ │ └── settings.json │ ├── Cargo.toml │ ├── readme-de.md │ ├── readme.md │ ├── src │ │ └── lib.rs │ └── www │ │ ├── package.json │ │ ├── src │ │ ├── bootstrap.ts │ │ ├── index.css │ │ ├── index.html │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── webpack.config.js ├── 120-tictactoe │ ├── .gitignore │ ├── Cargo.toml │ ├── readme.md │ ├── rustfmt.toml │ └── tictactoe_logic │ │ ├── Cargo.toml │ │ └── src │ │ ├── board_content.rs │ │ ├── board_index.rs │ │ ├── game.rs │ │ ├── lib.rs │ │ ├── row.rs │ │ └── square_content.rs ├── 200-guess-the-number-game │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── readme.md │ └── src │ │ └── main.rs ├── 205-take-n-from-m │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs ├── 210-clock │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── 220-fibonacci-iterator │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── 230-progress-iterator │ ├── .gitignore │ ├── .vscode │ │ └── snippets.code-snippets │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs ├── 240-multi-threading-example │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── 245-async-tokio │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── 246-fun-with-futures │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── 250-trait-objects │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── 255-traits-testing │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── 300-hello-diesel │ ├── .env │ ├── .gitignore │ ├── Cargo.toml │ ├── diesel.toml │ ├── migrations │ │ ├── 2022-09-25-081228_create_hero_types │ │ │ ├── down.sql │ │ │ └── up.sql │ │ └── 2022-09-25-081642_create_hero │ │ │ ├── down.sql │ │ │ └── up.sql │ ├── mydb.db │ ├── readme.md │ ├── rustfmt.toml │ └── src │ │ ├── main.rs │ │ ├── models.rs │ │ └── schema.rs ├── 400-hello-axum │ ├── .gitignore │ ├── Cargo.toml │ ├── requests.http │ └── src │ │ ├── api.rs │ │ ├── api_keys.rs │ │ └── main.rs ├── 450-request │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── fibonacci.rs │ │ └── main.rs ├── 500-error-handling │ ├── .gitignore │ ├── Cargo.toml │ ├── config.json │ └── src │ │ └── main.rs ├── 600-echo-server-tokio │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── 700-c-interop │ ├── area_calculator_c │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── area_calculator.c │ │ └── area_calculator.h │ └── area_calculator_rs │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src │ │ └── main.rs ├── 701-c-interop_2 │ ├── area_calculation_c │ │ ├── area_calculation_rs.h │ │ ├── justfile │ │ └── main.c │ └── area_calculation_rs │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── cbindgen.toml │ │ ├── justfile │ │ ├── my_header.h │ │ └── src │ │ └── lib.rs ├── 710-bus-graph │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ ├── main.rs │ │ ├── public_transport.rs │ │ └── transport_network.rs ├── 720-deref │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs └── 730-demystifying-iterators │ ├── .gitignore │ ├── generators │ ├── Cargo.toml │ └── src │ │ └── main.rs │ ├── iterator_tools │ ├── Cargo.toml │ └── src │ │ └── main.rs │ └── iterators_from_scratch │ ├── Cargo.toml │ └── src │ └── main.rs ├── hands-on-labs ├── 010-getting-started │ └── readme.md ├── 020-square-content │ └── readme.md ├── 030-simple-board-content │ └── readme.md ├── 040-generic-board-content │ └── readme.md ├── 050-cli │ └── readme.md ├── 060-board-index │ └── readme.md ├── 070-fillable-board │ └── readme.md ├── 080-ship-finder │ └── readme.md ├── 090-single-player-game │ └── readme.md ├── 1000-console-ttt │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── 1010-math-pyramid │ ├── math-pyramid │ │ ├── .gitignore │ │ ├── .vscode │ │ │ └── launch.json │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── args.rs │ │ │ ├── ascii_art.rs │ │ │ ├── main.rs │ │ │ └── pyramid.rs │ └── readme.md ├── 1020-tic-tac-toe │ ├── readme.md │ └── tic-tac-toe │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── src │ │ ├── board_content.rs │ │ ├── board_index.rs │ │ ├── game.rs │ │ ├── main.rs │ │ ├── row.rs │ │ └── square_content.rs ├── 1021-simple-ttt │ ├── readme.md │ └── simplettt │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── src │ │ ├── board_content.rs │ │ ├── board_index.rs │ │ ├── game.rs │ │ └── main.rs ├── 1030-7-segments │ ├── readme.md │ └── solution │ │ ├── .gitignore │ │ ├── .vscode │ │ └── launch.json │ │ ├── Cargo.toml │ │ ├── console_display │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ │ └── seven_segments │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── 200-web-api-getting-started │ └── readme.md ├── 210-game-repository │ └── readme.md ├── 220-web-api │ └── readme.md ├── 230-browser-client │ └── readme.md ├── 300-wasm-getting-started │ └── readme.md ├── 310-wasm-browser │ └── readme.md ├── 400-rusty-nerdle │ ├── .gitignore │ ├── Cargo.toml │ ├── readme.md │ ├── rusty-nerdle.png │ ├── src │ │ └── lib.rs │ └── www │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── src │ │ ├── board.ts │ │ ├── bootstrap.ts │ │ ├── constants.ts │ │ ├── game.ts │ │ ├── index.css │ │ ├── index.html │ │ ├── index.ts │ │ └── result.ts │ │ ├── tsconfig.json │ │ └── webpack.config.js ├── 999-final │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── settings.json │ ├── Cargo.toml │ ├── battleship_game_logic │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── board_filler.rs │ │ │ ├── board_index.rs │ │ │ ├── board_index_range.rs │ │ │ ├── fillable_board.rs │ │ │ ├── generic_board_content.rs │ │ │ ├── lib.rs │ │ │ ├── row.rs │ │ │ ├── ship_finder.rs │ │ │ ├── simple_board_content.rs │ │ │ ├── single_player_game.rs │ │ │ └── square_content.rs │ ├── battleship_wasm │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── src │ │ │ ├── lib.rs │ │ │ └── utils.rs │ │ ├── tests │ │ │ └── web.rs │ │ └── www │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ ├── src │ │ │ ├── bootstrap.ts │ │ │ ├── index.css │ │ │ ├── index.html │ │ │ └── index.ts │ │ │ ├── tsconfig.json │ │ │ └── webpack.config.js │ ├── battleship_web_api │ │ ├── Cargo.toml │ │ ├── public │ │ │ ├── index.css │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── requests.http │ │ └── src │ │ │ ├── game_repository.rs │ │ │ └── main.rs │ ├── console_game │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── rustfmt.toml └── readme.md └── slides ├── .editorconfig ├── .gitignore ├── .markdownlint.json ├── .vscode └── extensions.json ├── favicon ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── mstile-150x150.png ├── safari-pinned-tab.svg └── site.webmanifest ├── package-lock.json ├── package.json ├── readme.md ├── src ├── chapter │ ├── 000-intro.md │ ├── 002-hands-on-labs.md │ ├── 005-cargo.md │ ├── 010-common-concepts.md │ ├── 020-ownership.md │ ├── 030-handling-errors.md │ ├── 040-traits.md │ ├── 043-generics.md │ ├── 045-iterators.md │ ├── 050-advanced-topics.md │ ├── 060-rocket.md │ └── 070-wasm.md ├── example.md ├── images │ ├── cd-logo.png │ ├── memlayout-array.svg │ ├── memlayout-slice.svg │ ├── memlayout-string-str.svg │ ├── memlayout-vec.svg │ ├── rust-containers.jpeg │ └── rustlove.png ├── index.css ├── index.html └── index.js └── util ├── build.js └── clean.js /.github/workflows/build-slides.yaml: -------------------------------------------------------------------------------- 1 | name: Build slides 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - "slides/**" 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 🛎 16 | uses: actions/checkout@v2 17 | with: 18 | persist-credentials: false 19 | - name: 🔧 Use Node.js 14 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: '14' 23 | - name: 🔧 Install dependencies 24 | run: npm install 25 | working-directory: slides 26 | - name: 🏭 Build slides into website 27 | run: npm run build 28 | working-directory: slides 29 | - name: 🚀 Deploy 30 | uses: JamesIves/github-pages-deploy-action@4.1.4 31 | with: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | BRANCH: gh-pages 34 | FOLDER: slides/dist 35 | CLEAN: true 36 | -------------------------------------------------------------------------------- /.github/workflows/build-test-hol.yaml: -------------------------------------------------------------------------------- 1 | name: Build HoL, Run Tests 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - "hands-on-labs/999-final/**" 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 🛎 16 | uses: actions/checkout@v2 17 | with: 18 | persist-credentials: false 19 | - name: 🔧 Set Rust tools 20 | uses: actions-rs/toolchain@v1 21 | with: 22 | profile: minimal 23 | toolchain: stable 24 | components: rustfmt, clippy 25 | - name: 🏭 Build HoL 26 | run: cargo build 27 | working-directory: hands-on-labs/999-final 28 | - name: 🧪 Run tests 29 | run: cargo test 30 | working-directory: hands-on-labs/999-final 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Rust Linz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/100-foundations/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /examples/100-foundations/.vscode/0200-fundamentals-loops-rust.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "0200-loops-basic": { 3 | "prefix": "rs", 4 | "body": [ 5 | "use std::fs::File;", 6 | "use std::io::prelude::*;", 7 | "use std::io::BufReader;", 8 | "", 9 | "fn main() {", 10 | "let f = File::open(\"stairway.md\").unwrap();", 11 | " let mut reader = BufReader::new(f);", 12 | "", 13 | " let mut line = String::new();", 14 | " loop {", 15 | " let len = reader.read_line(&mut line).unwrap();", 16 | "", 17 | " if len == 0 {", 18 | " break;", 19 | " }", 20 | " println!(\"{} ({} bytes long)\", line, len);", 21 | "", 22 | " line.truncate(0);", 23 | " }", 24 | "}", 25 | "" 26 | ], 27 | "description": "9-loops" 28 | }, 29 | "0205-loops-iterator": { 30 | "prefix": "rs", 31 | "body": [ 32 | "let mut lines = BufReader::new(f).lines();", 33 | "loop {", 34 | " if let Some(line) = lines.next() {", 35 | " let line = line.unwrap();", 36 | " println!(\"{} ({} bytes long)\", line, line.len());", 37 | " } else {", 38 | " break;", 39 | " }", 40 | "}" 41 | ], 42 | "description": "10-loops" 43 | }, 44 | "0210-loops-while": { 45 | "prefix": "rs", 46 | "body": [ 47 | "let mut lines = BufReader::new(f).lines();", 48 | "while let Some(line) = lines.next() {", 49 | " let line = line.unwrap();", 50 | " println!(\"{} ({} bytes long)\", line, line.len());", 51 | "}" 52 | ], 53 | "description": "10-loops" 54 | }, 55 | "0215-loops-iteration": { 56 | "prefix": "rs", 57 | "body": [ 58 | "let lines = BufReader::new(f).lines();", 59 | "for line in lines {", 60 | " let line = line.unwrap();", 61 | " println!(\"{} ({} bytes long)\", line, line.len());", 62 | "}" 63 | ], 64 | "description": "10-loops" 65 | } 66 | } -------------------------------------------------------------------------------- /examples/100-foundations/.vscode/0500-ownership-rust.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "0500-ownership": { 3 | "prefix": "rs", 4 | "body": [ 5 | "fn print_numbers(numbers: Vec) {", 6 | " println!(\"{:?}\", numbers);", 7 | "}", 8 | "$0", 9 | "fn main() {", 10 | " let mut numbers = vec![1i32, 2, 3, 4, 5];", 11 | " numbers.push(6);", 12 | " numbers.push(7);", 13 | "", 14 | " print_numbers(numbers);", 15 | " // What happens here?", 16 | " //print_numbers(numbers);", 17 | "}", 18 | "" 19 | ], 20 | "description": "22-ownership" 21 | }, 22 | "0505-mutable-borrowing": { 23 | "prefix": "rs", 24 | "body": [ 25 | "fn create_numbers(numbers: &mut Vec) {", 26 | " for i in 0..10 {", 27 | " numbers.push(i);", 28 | " }", 29 | "}" 30 | ] 31 | }, 32 | "0510-call-create": { 33 | "prefix": "rs", 34 | "body": [ 35 | "let mut created_numbers = Vec::new();", 36 | "create_numbers(&mut created_numbers);", 37 | "print_numbers(created_numbers);" 38 | ] 39 | }, 40 | "0515-lifetimes": { 41 | "prefix": "rs", 42 | "body": [ 43 | "fn get_biggest<'a>(n: &'a i32, m: &'a i32) -> &'a i32 {", 44 | " if n > m {", 45 | " n", 46 | " } else {", 47 | " m", 48 | " }", 49 | "}", 50 | "" 51 | ], 52 | "description": "23-lifetimes" 53 | }, 54 | "0520-call-lifetimes": { 55 | "prefix": "rs", 56 | "body": [ 57 | "fn main() {", 58 | " let a = 5;", 59 | " let biggest;", 60 | " let b = 6;", 61 | " biggest = get_biggest(&a, &b);", 62 | "", 63 | " println!(\"{}\", biggest);", 64 | "}" 65 | ] 66 | } 67 | } -------------------------------------------------------------------------------- /examples/100-foundations/.vscode/0600-structs-lifetimes.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "0600-structs-lifetimes-starter": { 3 | "prefix": "rs", 4 | "body": [ 5 | "#[derive(Debug)]", 6 | "pub struct HighScores {", 7 | "}", 8 | "", 9 | "$0", 10 | "", 11 | "fn main() {", 12 | " let scores = [10, 30, 90, 30, 100, 20, 10, 0, 30, 40, 40, 70, 70];", 13 | " let high_scores = HighScores::new(&scores);", 14 | "}", 15 | "" 16 | ], 17 | "description": "28-structs-lifetimes" 18 | }, 19 | "0605-struct-with-lifetime": { 20 | "prefix": "rs", 21 | "body": [ 22 | "#[derive(Debug)]", 23 | "pub struct HighScores<'a> {", 24 | " scores: &'a [u32],", 25 | "}" 26 | ] 27 | }, 28 | "0610-structs-lifetimes": { 29 | "prefix": "rs", 30 | "body": [ 31 | "impl<'a> HighScores<'a> {", 32 | " pub fn new(scores: &'a [u32]) -> Self {", 33 | " Self { scores }", 34 | " }", 35 | "", 36 | " pub fn scores(&self) -> &'a [u32] {", 37 | " self.scores", 38 | " }", 39 | "", 40 | " pub fn latest(&self) -> Option {", 41 | " self.scores.last().copied()", 42 | " }", 43 | "", 44 | " pub fn personal_best(&self) -> Option {", 45 | " self.scores.iter().max().copied()", 46 | " }", 47 | "}" 48 | ], 49 | "description": "29-structs-lifetimes" 50 | } 51 | } -------------------------------------------------------------------------------- /examples/100-foundations/.vscode/0900-threading.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "40-thread": { 3 | "prefix": "rs", 4 | "body": [ 5 | "", 6 | "fn main() {", 7 | " let pairs = [(6, 45), (5, 50), (2, 12)];", 8 | " for (_take, _from) in pairs {", 9 | " let handle = thread::spawn(|| {", 10 | " let lotto = Lotto::new(6, 45);", 11 | " println!(\"{:?}\", lotto);", 12 | " });", 13 | "", 14 | " handle.join().unwrap();", 15 | " }", 16 | "}", 17 | "" 18 | ], 19 | "description": "40-thread" 20 | }, 21 | "41-thread": { 22 | "prefix": "rs", 23 | "body": [ 24 | "let pairs = [(6, 45), (5, 50), (2, 12)];", 25 | " for (take, from) in pairs {", 26 | " let handle = thread::spawn(move || {", 27 | " let lotto = Lotto::new(take, from);", 28 | " println!(\"{:?}\", lotto);", 29 | " });", 30 | "", 31 | " handle.join().unwrap();", 32 | " }" 33 | ], 34 | "description": "41-thread" 35 | }, 36 | "42-thread": { 37 | "prefix": "rs", 38 | "body": [ 39 | " let lottos = Mutex::new(Vec::::new());", 40 | " let lottos = Arc::new(lottos);", 41 | " let pairs = [(6, 45), (5, 50), (2, 12)];", 42 | "", 43 | " for (take, from) in pairs {", 44 | " let lottos = Arc::clone(&lottos);", 45 | " let handle = thread::spawn(move || {", 46 | " let lotto = Lotto::new(take, from);", 47 | " lottos.lock().unwrap().push(lotto);", 48 | " });", 49 | "", 50 | " handle.join().unwrap();", 51 | " }", 52 | "", 53 | " for lotto in lottos.lock().unwrap().iter() {", 54 | " println!(\"{:?}\", lotto);", 55 | " }" 56 | ], 57 | "description": "42-thread" 58 | } 59 | } -------------------------------------------------------------------------------- /examples/100-foundations/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "matklad.rust-analyzer", 8 | "vadimcn.vscode-lldb", 9 | "usernamehw.errorlens", 10 | "fill-labs.dependi" 11 | ], 12 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 13 | "unwantedRecommendations": [ 14 | "rust-lang.rust" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/100-foundations/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "rust-analyzer.checkOnSave.command": "clippy", 4 | "rust-analyzer.rustfmt.overrideCommand": [ 5 | "rustfmt" 6 | ], 7 | "rust-analyzer.files.excludeDirs": [ 8 | "backups" 9 | ], 10 | "workbench.colorTheme": "Default Light+" 11 | } -------------------------------------------------------------------------------- /examples/100-foundations/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "foundations" 3 | version = "0.1.0" 4 | authors = [ 5 | "stefan.baumgartner ", 6 | "Rainer Stropek " 7 | ] 8 | edition = "2021" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | rand = "0.8" 14 | futures = "0.3" 15 | async-std = "1.10" 16 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/01_variables_control_flow.rs: -------------------------------------------------------------------------------- 1 | use rand::Rng; 2 | 3 | fn add(i: i32, j: i32) -> i32 { 4 | i + j 5 | } 6 | 7 | fn main() { 8 | let a = 32; 9 | let b: i32 = 64; 10 | let c = add(a, b); 11 | 12 | println!("{}", c); 13 | 14 | let d = 0i32; 15 | let c = a + d; 16 | 17 | println!("{}", c); 18 | 19 | let mut c = b + d; 20 | c += a; 21 | 22 | println!("{}", c); 23 | 24 | { 25 | #[allow(unused_variables)] 26 | let c = c; // shadowing: c is now immutable = frozen 27 | // c += 4; // <<< Error; c is immutable 28 | } 29 | 30 | let num = rand::thread_rng().gen_range(0..10); 31 | 32 | let msg = match num { 33 | 5 => "So close", 34 | n if n < 6 => "Win!", 35 | _ => "Lost! :-(", 36 | }; 37 | 38 | println!("Draw {}, {}", num, msg); 39 | } 40 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/02_loops_main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::prelude::*; 3 | use std::io::BufReader; 4 | 5 | /* 6 | use std::fs::File; 7 | use std::io::prelude::*; 8 | use std::io::BufReader; 9 | 10 | fn main() { 11 | let f = File::open("stairway.md").unwrap(); 12 | let mut reader = BufReader::new(f); 13 | 14 | let mut line = String::new(); 15 | loop { 16 | let len = reader.read_line(&mut line).unwrap(); 17 | 18 | if len == 0 { 19 | break; 20 | } 21 | println!("{} ({} bytes long)", line, len); 22 | 23 | line.truncate(0); 24 | } 25 | } 26 | 27 | loop { 28 | if let Some(line) = lines.next() { 29 | let line = line.unwrap(); 30 | println!("{} ({} bytes long)", line, line.len()); 31 | } else { 32 | break; 33 | } 34 | } 35 | 36 | let mut lines = BufReader::new(f).lines(); 37 | while let Some(line) = lines.next() { 38 | let line = line.unwrap(); 39 | println!("{} ({} bytes long)", line, line.len()); 40 | } 41 | 42 | */ 43 | 44 | fn main() { 45 | let f = File::open("stairway.md").unwrap(); 46 | let lines = BufReader::new(f).lines(); 47 | for line in lines { 48 | let line = line.unwrap(); 49 | println!("{} ({} bytes long)", line, line.len()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/03_tuples_main.rs: -------------------------------------------------------------------------------- 1 | // Tuples can be used as function arguments and as return values 2 | fn reverse(pair: (i32, bool)) -> (bool, i32) { 3 | // `let` can be used to bind the members of a tuple to variables 4 | let (integer, boolean) = pair; 5 | 6 | (boolean, integer) 7 | } 8 | 9 | // The following struct is for the activity. 10 | #[derive(Debug)] 11 | struct Matrix(f32, f32, f32, f32); 12 | 13 | fn main() { 14 | // A tuple with a bunch of different types 15 | let long_tuple = ( 16 | 1u8, 2u16, 3u32, 4u64, -1i8, -2i16, -3i32, -4i64, 0.1f32, 0.2f64, 'a', true, 17 | ); 18 | 19 | // Values can be extracted from the tuple using tuple indexing 20 | println!("long tuple first value: {}", long_tuple.0); 21 | println!("long tuple second value: {}", long_tuple.1); 22 | 23 | // Tuples can be tuple members 24 | let tuple_of_tuples = ((1u8, 2u16, 2u32), (4u64, -1i8), -2i16); 25 | 26 | // Tuples are printable 27 | println!("tuple of tuples: {:?}", tuple_of_tuples); 28 | 29 | // But long Tuples cannot be printed 30 | // let too_long_tuple = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); 31 | // println!("too long tuple: {:?}", too_long_tuple); 32 | // TODO ^ Uncomment the above 2 lines to see the compiler error 33 | 34 | let pair = (1, true); 35 | println!("pair is {:?}", pair); 36 | 37 | println!("the reversed pair is {:?}", reverse(pair)); 38 | 39 | // To create one element tuples, the comma is required to tell them apart 40 | // from a literal surrounded by parentheses 41 | println!("one element tuple: {:?}", (5u32,)); 42 | println!("just an integer: {:?}", (5u32)); 43 | 44 | //tuples can be destructured to create bindings 45 | let tuple = (1, "hello", 4.5, true); 46 | 47 | let (a, b, c, d) = tuple; 48 | println!("{:?}, {:?}, {:?}, {:?}", a, b, c, d); 49 | 50 | let matrix = Matrix(1.1, 1.2, 2.1, 2.2); 51 | println!("{:?}", matrix); 52 | } 53 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/05_vec_main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut vecc = vec![1, 2, 3, 4]; 3 | 4 | let mut vecc_collected: Vec = (1..5).collect(); 5 | 6 | vecc_collected.push(5); 7 | 8 | println!("{:?}", vecc_collected); 9 | 10 | // Turbofish 11 | let mut vecc_collected = (1..5).collect::>(); 12 | 13 | vecc.push(5); 14 | 15 | vecc_collected.push(6); 16 | 17 | for i in vecc_collected { 18 | println!("> {}", i); 19 | } 20 | let mut i = 0; 21 | while i < vecc.len() { 22 | println!(": {}", vecc[i]); 23 | i += 1; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/06_structs_main.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | #[derive(PartialEq, Eq, Debug, Clone)] 4 | pub struct Clock { 5 | hours: i32, 6 | minutes: i32, 7 | } 8 | 9 | impl Clock { 10 | pub fn new(hours: i32, minutes: i32) -> Self { 11 | Self { hours, minutes }.normalize() 12 | } 13 | 14 | pub fn add_minutes(&self, minutes: i32) -> Self { 15 | Self { 16 | hours: self.hours, 17 | minutes: self.minutes + minutes, 18 | } 19 | .normalize() 20 | } 21 | 22 | fn normalize(&mut self) -> Self { 23 | let mut hours = (self.hours + self.minutes / 60) % 24; 24 | let mut minutes = self.minutes % 60; 25 | if minutes < 0 { 26 | minutes += 60; 27 | hours -= 1; 28 | } 29 | if hours < 0 { 30 | hours = (hours + 24) % 24; 31 | } 32 | Self { hours, minutes } 33 | } 34 | } 35 | 36 | impl fmt::Display for Clock { 37 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 38 | write!(f, "{:0>2}:{:0>2}", self.hours, self.minutes) 39 | } 40 | } 41 | 42 | fn main() { 43 | let c = Clock::new(24, 0); 44 | let d = Clock::new(0, 0); 45 | 46 | if c == d { 47 | println!("It's the same!"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/07_ownership.rs: -------------------------------------------------------------------------------- 1 | fn print_numbers(numbers: &Vec) { 2 | println!("{:?}", numbers); 3 | } 4 | 5 | fn create_numbers(numbers: &mut Vec) { 6 | for i in 0..10 { 7 | numbers.push(i); 8 | } 9 | } 10 | 11 | fn main() { 12 | let inital_numbers = vec![1i32, 2, 3, 4, 5]; 13 | 14 | let mut numbers = inital_numbers; 15 | numbers.push(6); 16 | numbers.push(7); 17 | 18 | print_numbers(&numbers); 19 | // What happens here? 20 | print_numbers(&numbers); 21 | 22 | let mut created_numbers = Vec::new(); 23 | create_numbers(&mut created_numbers); 24 | print_numbers(&created_numbers); 25 | } 26 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/08_lifetimes.rs: -------------------------------------------------------------------------------- 1 | fn get_biggest<'a>(n: &'a i32, m: &'a i32) -> &'a i32 { 2 | if n > m { 3 | n 4 | } else { 5 | m 6 | } 7 | } 8 | 9 | fn main() { 10 | let a = 5; 11 | let biggest; 12 | let b = 6; 13 | biggest = get_biggest(&a, &b); 14 | 15 | println!("{}", biggest); 16 | } 17 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/09_lifetimes_structs_main.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct HighScores<'a> { 3 | scores: &'a [u32], 4 | } 5 | 6 | impl<'a> HighScores<'a> { 7 | pub fn new(scores: &'a [u32]) -> Self { 8 | Self { scores } 9 | } 10 | 11 | pub fn scores(&self) -> &'a [u32] { 12 | self.scores 13 | } 14 | 15 | pub fn latest(&self) -> Option { 16 | self.scores.last().copied() 17 | } 18 | 19 | pub fn personal_best(&self) -> Option { 20 | self.scores.iter().max().copied() 21 | } 22 | 23 | pub fn personal_top_three(&self) -> Vec { 24 | let mut v = self.scores.to_vec(); 25 | v.sort(); 26 | v.iter().rev().take(3).copied().collect() 27 | } 28 | } 29 | 30 | fn main() { 31 | let scores = [10, 30, 90, 30, 100, 20, 10, 0, 30, 40, 40, 70, 70]; 32 | let high_scores = HighScores::new(&scores); 33 | 34 | println!("{:?}", high_scores.personal_top_three()); 35 | } 36 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/10_enums_iterators_enumerators.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | struct Individual { 3 | name: String, 4 | } 5 | 6 | enum Room { 7 | Occupied(Individual), 8 | Vacant, 9 | } 10 | 11 | type Hotel = Vec; 12 | 13 | fn main() { 14 | let mut hotel = Hotel::new(); 15 | 16 | let alice = Individual { 17 | name: "Alice".to_string(), 18 | }; 19 | 20 | let barbara = Individual { 21 | name: "Barbara".to_string(), 22 | }; 23 | 24 | hotel.push(Room::Occupied(alice)); 25 | hotel.push(Room::Vacant); 26 | hotel.push(Room::Occupied(barbara)); 27 | 28 | for room in hotel.iter() { 29 | match *room { 30 | Room::Vacant => println!("Available!"), 31 | Room::Occupied(_) => println!("Taken, sorry."), 32 | } 33 | } 34 | 35 | for (i, house) in hotel.iter().enumerate() { 36 | match *house { 37 | Room::Vacant => println!("No. {} is available!", i), 38 | Room::Occupied(_) => println!("No {} is taken.", i), 39 | } 40 | } 41 | 42 | //step 3, introduce scoping, ref keuword 43 | use Room::{Occupied, Vacant}; 44 | 45 | for (i, house) in hotel.iter().enumerate() { 46 | match *house { 47 | Vacant => println!("No. {} is available!", i), 48 | Occupied(ref occupant) => println!("No {} is taken by {:?}.", i, occupant.name), 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/11_error_handling.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::File, 3 | io::{self, Read}, 4 | }; 5 | 6 | /// Steps: 7 | /// 1. Make param optional 8 | /// 2. Match vs unwrap 9 | /// 3. Bubble up 10 | 11 | fn read_username_from_file(path: &str) -> Result { 12 | let f = File::open(path); 13 | 14 | let mut f = match f { 15 | Ok(file) => file, 16 | Err(e) => return Err(e), 17 | }; 18 | 19 | let mut s = String::new(); 20 | 21 | match f.read_to_string(&mut s) { 22 | Ok(_) => Ok(s), 23 | Err(err) => Err(err), 24 | } 25 | } 26 | 27 | fn main() { 28 | match read_username_from_file("username.txt") { 29 | Ok(result) => println!("Username is {}", result), 30 | Err(err) => println!("Error occured! {}", err), 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/12_iterators.rs: -------------------------------------------------------------------------------- 1 | use rand::Rng; 2 | 3 | fn main() { 4 | let mut vec = Vec::::new(); 5 | 6 | for _ in 0..10 { 7 | let random_number = rand::thread_rng().gen_range(0..=50); 8 | if random_number % 3 != 0 { 9 | vec.push(random_number); 10 | } 11 | } 12 | 13 | println!("{:?}", vec); 14 | 15 | let random_range: Vec = (0..10) 16 | .map(|_| rand::thread_rng().gen_range(0..=50)) 17 | .filter(|el| el % 3 != 0) 18 | .collect::>(); 19 | 20 | println!("{:?}", random_range); 21 | } 22 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/13_into_iterator.rs: -------------------------------------------------------------------------------- 1 | use rand::{prelude::IteratorRandom, thread_rng}; 2 | 3 | #[derive(Default)] 4 | struct Lotto { 5 | numbers: Vec, 6 | } 7 | 8 | impl Lotto { 9 | pub fn new(amount: usize, max: usize) -> Self { 10 | let pot = 1..=max; 11 | let mut rng = thread_rng(); 12 | Self { 13 | numbers: pot.choose_multiple(&mut rng, amount), 14 | } 15 | } 16 | } 17 | 18 | impl IntoIterator for Lotto { 19 | type Item = as IntoIterator>::Item; 20 | type IntoIter = as IntoIterator>::IntoIter; 21 | 22 | fn into_iter(self) -> Self::IntoIter { 23 | self.numbers.into_iter() 24 | } 25 | } 26 | 27 | fn main() { 28 | let lotto = Lotto::new(6, 45); 29 | for i in lotto { 30 | println!("{}", i); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/14_threading.rs: -------------------------------------------------------------------------------- 1 | use rand::{prelude::IteratorRandom, thread_rng}; 2 | use std::{ 3 | sync::{Arc, Mutex}, 4 | thread, 5 | }; 6 | 7 | #[derive(Default, Debug)] 8 | struct Lotto { 9 | numbers: Vec, 10 | } 11 | 12 | impl Lotto { 13 | pub fn new(amount: usize, max: usize) -> Self { 14 | let pot = 1..=max; 15 | let mut rng = thread_rng(); 16 | Self { 17 | numbers: pot.choose_multiple(&mut rng, amount), 18 | } 19 | } 20 | } 21 | 22 | impl IntoIterator for Lotto { 23 | type Item = as IntoIterator>::Item; 24 | type IntoIter = as IntoIterator>::IntoIter; 25 | 26 | fn into_iter(self) -> Self::IntoIter { 27 | self.numbers.into_iter() 28 | } 29 | } 30 | fn main() { 31 | let lottos = Mutex::new(Vec::::new()); 32 | let lottos = Arc::new(lottos); 33 | let pairs = [(6, 45), (5, 50), (2, 12)]; 34 | 35 | for (take, from) in pairs { 36 | let lottos = Arc::clone(&lottos); 37 | let handle = thread::spawn(move || { 38 | let lotto = Lotto::new(take, from); 39 | lottos.lock().unwrap().push(lotto); 40 | }); 41 | 42 | handle.join().unwrap(); 43 | } 44 | 45 | for lotto in lottos.lock().unwrap().iter() { 46 | println!("{:?}", lotto); 47 | } 48 | } 49 | 50 | /* 51 | // 2 52 | fn main() { 53 | let pairs = [(6, 45), (5, 50), (2, 12)]; 54 | for (take, from) in pairs { 55 | let handle = thread::spawn(move || { 56 | let lotto = Lotto::new(take, from); 57 | println!("{:?}", lotto); 58 | }); 59 | 60 | handle.join().unwrap(); 61 | } 62 | } 63 | */ 64 | 65 | /* 66 | //1 67 | fn main() { 68 | let pairs = [(6, 45), (5, 50), (2, 12)]; 69 | for (_take, _from) in pairs { 70 | let handle = thread::spawn(|| { 71 | let lotto = Lotto::new(6, 45); 72 | println!("{:?}", lotto); 73 | }); 74 | 75 | handle.join().unwrap(); 76 | } 77 | } 78 | 79 | */ 80 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/15_generics_boxing.rs: -------------------------------------------------------------------------------- 1 | use std::iter::FromIterator; 2 | 3 | #[derive(Debug)] 4 | struct LinkedList { 5 | head: Option>>, 6 | len: usize, 7 | } 8 | 9 | #[derive(Debug)] 10 | struct Node { 11 | value: T, 12 | next: Option>>, 13 | } 14 | 15 | impl LinkedList { 16 | pub fn new() -> Self { 17 | Self { head: None, len: 0 } 18 | } 19 | 20 | pub fn len(&self) -> usize { 21 | self.len 22 | } 23 | 24 | pub fn push(&mut self, element: T) { 25 | let top = Box::new(Node { 26 | value: element, 27 | next: self.head.take(), 28 | }); 29 | self.head = Some(top); 30 | self.len += 1; 31 | } 32 | 33 | pub fn pop(&mut self) -> Option { 34 | match self.head.take() { 35 | Some(x) => { 36 | self.head = x.next; 37 | self.len -= 1; 38 | Some(x.value) 39 | } 40 | None => None, 41 | } 42 | } 43 | 44 | pub fn rev(mut self) -> LinkedList { 45 | let mut list = LinkedList::::new(); 46 | while let Some(x) = self.pop() { 47 | list.push(x); 48 | } 49 | list 50 | } 51 | } 52 | 53 | impl FromIterator for LinkedList { 54 | fn from_iter>(iter: I) -> Self { 55 | let mut list = LinkedList::new(); 56 | for val in iter { 57 | list.push(val); 58 | } 59 | list 60 | } 61 | } 62 | 63 | impl Iterator for Node { 64 | fn next(&mut self) -> Option>> { 65 | self.next 66 | } 67 | } 68 | 69 | impl Iterator for LinkedList { 70 | type Item = T; 71 | 72 | fn next(&mut self) -> Option { 73 | self.pop() 74 | } 75 | } 76 | 77 | fn main() { 78 | let c = vec![12, 23, 2131, 1231, 4352353, 123]; 79 | let len = c.len(); 80 | let list = LinkedList::from_iter(c); 81 | 82 | println!("{:#?}", list); 83 | 84 | assert_eq!(len, list.len()); 85 | 86 | let mut list = list.rev(); 87 | while let Some(x) = list.pop() { 88 | println!("{}", x); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/16_more_traits.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | struct MaintenanceHours { 4 | hours: f32, 5 | rate: f32, 6 | } 7 | 8 | pub trait Billable { 9 | fn bill(&self) -> f32 { 10 | 0f32 11 | } 12 | } 13 | 14 | impl Billable for MaintenanceHours { 15 | fn bill(&self) -> f32 { 16 | self.hours * self.rate 17 | } 18 | } 19 | 20 | fn print_bill(bill: &impl Billable) { 21 | println!("{}", bill.bill()); 22 | } 23 | 24 | fn main() { 25 | let hours = MaintenanceHours { 26 | hours: 30f32, 27 | rate: 200f32, 28 | }; 29 | print_bill(&hours); 30 | } 31 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/17_trait_objects.rs: -------------------------------------------------------------------------------- 1 | // https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b5a946315c925965ccbe5d683e726a1c 2 | 3 | trait Greeter { 4 | fn greet(&self) -> String; 5 | } 6 | 7 | struct Person { 8 | name: String, 9 | } 10 | 11 | impl Greeter for Person { 12 | fn greet(&self) -> String { 13 | format!("Hey {}!", self.name) 14 | } 15 | } 16 | 17 | struct Dog; 18 | 19 | impl Greeter for Dog { 20 | fn greet(&self) -> String { 21 | "Who is a good boy?".to_string() 22 | } 23 | } 24 | 25 | fn get_a_greeter(val: u8) -> Box { 26 | if val < 5 { 27 | Box::new(Person { 28 | name: "unknown".to_string(), 29 | }) 30 | } else { 31 | Box::new(Dog {}) 32 | } 33 | } 34 | 35 | fn main() { 36 | println!("{}", get_a_greeter(5).greet()); 37 | } 38 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/18_fibonacci.rs: -------------------------------------------------------------------------------- 1 | struct Fibonacci { 2 | curr: u32, 3 | next: u32, 4 | } 5 | 6 | impl Iterator for Fibonacci { 7 | // We can refer to this type using Self::Item 8 | type Item = u32; 9 | fn next(&mut self) -> Option { 10 | let new_next = self.curr + self.next; 11 | 12 | self.curr = self.next; 13 | self.next = new_next; 14 | 15 | Some(self.curr) 16 | } 17 | } 18 | 19 | // Returns a Fibonacci sequence generator 20 | fn fibonacci() -> Fibonacci { 21 | Fibonacci { curr: 0, next: 1 } 22 | } 23 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/19-reverse.rs: -------------------------------------------------------------------------------- 1 | trait Greeter { 2 | fn greet(&self) -> String; 3 | } 4 | 5 | struct Person { 6 | name: String, 7 | } 8 | 9 | impl Greeter for Person { 10 | fn greet(&self) -> String { 11 | format!("Hey {}!", self.name) 12 | } 13 | } 14 | 15 | struct Dog; 16 | 17 | impl Greeter for Dog { 18 | fn greet(&self) -> String { 19 | "Who is a good boy?".to_string() 20 | } 21 | } 22 | 23 | fn get_a_greeter(val: u8) -> Box { 24 | if val < 5 { 25 | Box::new(Person { 26 | name: "unknown".to_string(), 27 | }) 28 | } else { 29 | Box::new(Dog {}) 30 | } 31 | } 32 | 33 | fn let_the_greeter_greet(greeter: &dyn Greeter) { 34 | println!("{}", greeter.greet()); 35 | } 36 | 37 | /// Reverses an unsigned integer number (e.g. 123 -> 321) 38 | fn reverse(a: u32) -> u32 { 39 | let radix = 10; 40 | let mut n = a; 41 | let mut reversed = 10; 42 | 43 | while !n.is_zero() { 44 | reversed = reversed * radix + n % radix; 45 | n = n / radix; 46 | } 47 | 48 | reversed 49 | } 50 | 51 | fn main() { 52 | let greeter = get_a_greeter(4); 53 | let_the_greeter_greet(greeter.as_ref()); 54 | } 55 | -------------------------------------------------------------------------------- /examples/100-foundations/backups/20-async.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::File, 3 | io::{self, Read}, 4 | }; 5 | 6 | use futures::{FutureExt, join, select}; 7 | 8 | use async_std::task; 9 | 10 | async fn read_file(path: &str) -> Result { 11 | let mut file = File::open(path)?; 12 | let mut buf = String::new(); 13 | file.read_to_string(&mut buf)?; 14 | Ok(buf) 15 | } 16 | 17 | async fn do_something_wrong() { 18 | let res_one = read_file("username.txt").await; 19 | let res_two = read_file("stairway.md").await; 20 | println!("{}", res_one.unwrap()); 21 | println!("{}", res_two.unwrap()); 22 | } 23 | 24 | 25 | async fn do_something() { 26 | let one = read_file("username.txt"); 27 | let two = read_file("stairway.md"); 28 | let (res_one, res_two) = join!(one, two); 29 | println!("{}", res_one.unwrap()); 30 | println!("{}", res_two.unwrap()); 31 | } 32 | 33 | 34 | fn main() { 35 | task::block_on(do_something()) 36 | } 37 | 38 | -------------------------------------------------------------------------------- /examples/100-foundations/readme.md: -------------------------------------------------------------------------------- 1 | # Rust Fundamentals 2 | 3 | ## Introduction 4 | 5 | Use this example to introduce Rust language fundamentals to beginners. 6 | 7 | This exercise contains [snippets](.vscode) that can be used during demonstration or by attendees to follow along. 8 | 9 | ## Getting Started 10 | 11 | * Create a new Rust app: `cargo new foundations` 12 | * Add crates required in this exercise to generated *Cargo.toml*: 13 | 14 | ```toml 15 | [dependencies] 16 | rand = "0.8" 17 | futures = "0.3" 18 | async-std = "1.10" 19 | ``` 20 | 21 | * Copy [*.vscode*](.vscode) folder into created app directory 22 | -------------------------------------------------------------------------------- /examples/100-foundations/src/main.rs: -------------------------------------------------------------------------------- 1 | use rand::{prelude::IteratorRandom, thread_rng}; 2 | use std::{ 3 | fs::File, 4 | io::{BufRead, BufReader}, 5 | sync::{Arc, Mutex}, 6 | thread, 7 | }; 8 | 9 | #[derive(Default, Debug)] 10 | struct Lotto { 11 | numbers: Vec, 12 | } 13 | 14 | impl Lotto { 15 | pub fn new(amount: usize, max: usize) -> Self { 16 | let pot = 1..=max; 17 | let mut rng = thread_rng(); 18 | Self { 19 | numbers: pot.choose_multiple(&mut rng, amount), 20 | } 21 | } 22 | } 23 | 24 | impl IntoIterator for Lotto { 25 | type Item = as IntoIterator>::Item; 26 | type IntoIter = as IntoIterator>::IntoIter; 27 | 28 | fn into_iter(self) -> Self::IntoIter { 29 | self.numbers.into_iter() 30 | } 31 | } 32 | fn main() { 33 | let lottos = Mutex::new(Vec::::new()); 34 | let lottos = Arc::new(lottos); 35 | let pairs = [(6, 45), (5, 50), (2, 12)]; 36 | let mut handles = vec![]; 37 | 38 | for (take, from) in pairs { 39 | let lottos = Arc::clone(&lottos); 40 | let handle = thread::spawn(move || { 41 | let lotto = Lotto::new(take, from); 42 | lottos.lock().unwrap().push(lotto); 43 | }); 44 | handles.push(handle); 45 | } 46 | 47 | for handle in handles { 48 | handle.join().unwrap(); 49 | } 50 | 51 | for lotto in lottos.lock().unwrap().iter() { 52 | println!("{:?}", lotto); 53 | } 54 | } 55 | 56 | fn isTruthy() -> bool { 57 | true 58 | } 59 | 60 | /* 61 | // 2 62 | fn main() { 63 | let pairs = [(6, 45), (5, 50), (2, 12)]; 64 | for (take, from) in pairs { 65 | let handle = thread::spawn(move || { 66 | let lotto = Lotto::new(take, from); 67 | println!("{:?}", lotto); 68 | }); 69 | 70 | handle.join().unwrap(); 71 | } 72 | } 73 | */ 74 | 75 | /* 76 | //1 77 | fn main() { 78 | let pairs = [(6, 45), (5, 50), (2, 12)]; 79 | for (_take, _from) in pairs { 80 | let handle = thread::spawn(|| { 81 | let lotto = Lotto::new(6, 45); 82 | println!("{:?}", lotto); 83 | }); 84 | 85 | handle.join().unwrap(); 86 | } 87 | } 88 | 89 | */ 90 | -------------------------------------------------------------------------------- /examples/100-foundations/stairway.md: -------------------------------------------------------------------------------- 1 | There's a lady who's sure all that glitters is gold 2 | And she's buying a stairway to heaven 3 | When she gets there she knows, if the stores are all closed 4 | With a word she can get what she came for 5 | Ooh, ooh, and she's buying a stairway to heaven 6 | There's a sign on the wall, but she wants to be sure 7 | 'Cause you know sometimes words have two meanings 8 | In a tree by the brook, there's a songbird who sings 9 | Sometimes all of our thoughts are misgiven 10 | You know 11 | There's a feeling I get when I look to the west 12 | And my spirit is crying for leaving 13 | In my thoughts I have seen rings of smoke through the trees 14 | And the voices of those who stand looking 15 | That's you 16 | And it's whispered that soon, if we all call the tune 17 | Then the piper will lead us to reason 18 | And a new day will dawn for those who stand long 19 | And the forests will echo with laughter 20 | Remember laughter? 21 | Oh yeah, yeah, yeah... 22 | And it makes me wonder 23 | If there's a bustle in your hedgerow, don't be alarmed now 24 | It's just a spring clean for the May queen 25 | Yes, there are two paths you can go by, but in the long run 26 | There's still time to change the road you're on 27 | Your head is humming and it won't go, in case you don't know 28 | The piper's calling you to join him 29 | Dear lady, can you hear the wind blow, and did you know 30 | Your stairway lies on the whispering wind? 31 | And as we wind on down the road 32 | Our shadows taller than our soul 33 | There walks a lady we all know 34 | Who shines white light and wants to show 35 | How everything still turns to gold 36 | And if you listen very hard 37 | The tune will come to you at last 38 | When all is one and one is all, that's what it is 39 | To be a rock and not to roll, oh yeah 40 | And she's buying a stairway to heaven -------------------------------------------------------------------------------- /examples/100-foundations/username.txt: -------------------------------------------------------------------------------- 1 | ddprrt -------------------------------------------------------------------------------- /examples/110-wasm/.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | target 3 | www/node_modules 4 | www/.bin 5 | www/.cache 6 | www/dist 7 | Cargo.lock 8 | www/package-lock.json 9 | node/node_modules 10 | node/package-lock.json 11 | -------------------------------------------------------------------------------- /examples/110-wasm/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.diagnostics.disabled": [ 3 | "missing-unsafe" 4 | ] 5 | } -------------------------------------------------------------------------------- /examples/110-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm_examples" 3 | version = "0.1.0" 4 | authors = ["Rainer "] 5 | edition = "2021" 6 | 7 | # Crate types cdylib and rlib are important. 8 | # cdylib is for creating a .wasm file. rlib is important for testing with "wasm-pack test". 9 | # Read more at https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/template-deep-dive/cargo-toml.html#1-crate-type 10 | [lib] 11 | crate-type = ["cdylib", "rlib"] 12 | 13 | [dependencies] 14 | # wasm-bindgen is the most important dependency. Read more at 15 | # https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/template-deep-dive/cargo-toml.html#2-wasm-bindgen-dependency. 16 | # "serde-serialize" is important for working with JsValue. Read more at 17 | # https://rustwasm.github.io/docs/wasm-bindgen/reference/arbitrary-data-with-serde.html 18 | wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } 19 | console_error_panic_hook = "0.1" 20 | serde = { version = "1.0", features = ["derive"] } 21 | js-sys = "0.3" 22 | serde-wasm-bindgen = "0.6" 23 | web-sys = { version = "0.3", features = [ 24 | 'Document', 25 | 'Element', 26 | 'HtmlElement', 27 | 'Node', 28 | 'Window', 29 | 'HtmlButtonElement', 30 | 'HtmlInputElement', 31 | 'HtmlUListElement', 32 | 'HtmlLiElement', 33 | ] } 34 | 35 | [dev-dependencies] 36 | wasm-bindgen-test = "0.3" 37 | -------------------------------------------------------------------------------- /examples/110-wasm/justfile: -------------------------------------------------------------------------------- 1 | build: 2 | wasm-pack build --target browser -d ./pkg 3 | 4 | build-node: 5 | wasm-pack build --target nodejs -d ./pkg-node; cd node; npm install 6 | -------------------------------------------------------------------------------- /examples/110-wasm/node/app.ts: -------------------------------------------------------------------------------- 1 | import * as we from 'wasm_examples'; 2 | 3 | we.writeToConsole('Hello World!'); 4 | -------------------------------------------------------------------------------- /examples/110-wasm/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wasm_examples": "file:../pkg-node" 13 | }, 14 | "devDependencies": { 15 | "ts-node": "^10.9.1", 16 | "typescript": "^5.0.4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/110-wasm/node/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 4 | "module": "commonjs", /* Specify what module code is generated. */ 5 | "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 6 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 7 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 8 | "strict": true, /* Enable all strict type-checking options. */ 9 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 10 | } 11 | } -------------------------------------------------------------------------------- /examples/110-wasm/readme.md: -------------------------------------------------------------------------------- 1 | # Rust Wasm Sample 2 | 3 | ## Important Links 4 | 5 | * Interesting blob posts 6 | * Lin Clark: [Making WebAssembly better for Rust & for all languages](https://hacks.mozilla.org/2018/03/making-webassembly-better-for-rust-for-all-languages/) (March 2018) 7 | * Alex Crichton: [JavaScript to Rust and Back Again: A *wasm-bindgen* Tale](https://hacks.mozilla.org/2018/04/javascript-to-rust-and-back-again-a-wasm-bindgen-tale/) (April 2018) 8 | * Ashley Williams: [Hello *wasm-pack*!](https://hacks.mozilla.org/2018/04/hello-wasm-pack/) (April 2018) 9 | * Lin Clark: [Calls between JavaScript and WebAssembly are finally fast 🎉](https://hacks.mozilla.org/2018/10/calls-between-javascript-and-webassembly-are-finally-fast-%f0%9f%8e%89/) (October 2018) 10 | * Documentation 11 | * [The *wasm-bindgen* Guide](https://rustwasm.github.io/docs/wasm-bindgen/introduction.html) 12 | * [Hello *wasm-pack*!](https://rustwasm.github.io/docs/wasm-pack/) 13 | * Crates 14 | * [*wasm-bindgen*](https://crates.io/crates/wasm-bindgen) 15 | * [*wasm-bindgen-test*](https://crates.io/crates/wasm-bindgen-test) 16 | * [*js-sys*](https://crates.io/crates/js-sys) 17 | * [*serde-wasm-bindgen*](https://crates.io/crates/serde-wasm-bindgen) 18 | * [*web_sys*](https://crates.io/crates/web_sys) 19 | 20 | ## Storyboard 21 | 22 | We will follow the [*Getting Started* guide in the Rust Wasm book](https://rustwasm.github.io/docs/book/game-of-life/hello-world.html) to create our new package. Run `cargo generate --git https://github.com/rustwasm/wasm-pack-template` and use *wasm_examples* as your project name. 23 | 24 | If you want, you can delete files that are not relevant for learning Rust (e.g. readme-files, license-files, etc.). 25 | 26 | Use [*wasm-pack*](https://rustwasm.github.io/wasm-pack/book/) to build the code: `wasm-pack build` 27 | 28 | Use `wasm-pack test --chrome` to verify that all tests pass. 29 | 30 | Discuss the anatomy of the generated project. After this, copy the sample code from this folder into your project. 31 | 32 | The sample code in [*lib.rs*](src/lib.rs) and [*index.ts*](www/src/index.ts) demonstrates various aspects of Rust Wasm. Do a code walkthrough region-by-region. In longer workshops and trainings you can also write the code region-by-region and discuss it in details. 33 | 34 | ## Want More? 35 | 36 | Examples for higher-level frameworks: 37 | 38 | * [stdweb](https://github.com/koute/stdweb) 39 | * [Yew](https://yew.rs/) 40 | * [Percy](https://chinedufn.github.io/percy/) 41 | -------------------------------------------------------------------------------- /examples/110-wasm/tests/web.rs: -------------------------------------------------------------------------------- 1 | //! Test suite for the Web and headless browsers. 2 | 3 | #![cfg(target_arch = "wasm32")] 4 | 5 | extern crate wasm_bindgen_test; 6 | use wasm_bindgen_test::*; 7 | 8 | wasm_bindgen_test_configure!(run_in_browser); 9 | 10 | #[wasm_bindgen_test] 11 | fn pass() { 12 | assert_eq!(1 + 1, 2); 13 | } 14 | -------------------------------------------------------------------------------- /examples/110-wasm/www/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wasm-examples", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack --config webpack.config.js", 8 | "start": "webpack serve" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "base64-loader": "^1.0.0", 14 | "css-loader": "^6.5.0", 15 | "hello-wasm-pack": "^0.1.0", 16 | "html-webpack-plugin": "^5.5.0", 17 | "style-loader": "^3.3.1", 18 | "ts-loader": "^9.2.6", 19 | "typescript": "^5.0.4", 20 | "webpack": "^5.60.0", 21 | "webpack-cli": "^5.0.1", 22 | "webpack-dev-server": "^4.3.1" 23 | }, 24 | "dependencies": { 25 | "cash-dom": "^8.1.0", 26 | "wasm_examples": "file:../pkg" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/110-wasm/www/src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | // A dependency graph that contains any wasm must all be imported 2 | // asynchronously. This `bootstrap.js` file does the single async import, so 3 | // that no one else needs to worry about it again. 4 | import("./index") 5 | .catch(e => console.error("Error importing `index.js`:", e)); 6 | -------------------------------------------------------------------------------- /examples/110-wasm/www/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 3 | } 4 | 5 | h1 { 6 | font-size: 20px; 7 | } 8 | 9 | h2 { 10 | font-size: 14px; 11 | margin-top: 20px; 12 | margin-bottom: 0; 13 | } 14 | 15 | p { 16 | font-size: 12px; 17 | } 18 | 19 | canvas { 20 | border: 1px solid black; 21 | padding: 1px; 22 | } 23 | -------------------------------------------------------------------------------- /examples/110-wasm/www/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Wasm Examples 8 | 9 | 10 |

Wasm Examples

11 | 12 |

13 | Most demos write output to console. Please open developer tools and 14 | view console output while trying examples below. 15 |

16 | 17 |

Calling Rust From JS And Vice Versa

18 | 19 | 20 | 21 |

Numeric Parameters and Return Values

22 | 23 | 24 |

Boolean parameters

25 | 26 | 27 |

Handling Panics

28 | 29 | 30 |

Number Slices

31 |
32 | 33 | 34 |

String Handling

35 | 36 | 37 |

Communicating With Custom Types

38 | 39 | 40 | 41 | 42 |

Pointers To Shared Buffers

43 | 44 | 45 |

DOM Manipulation With web_sys

46 | 47 |
    48 | 49 | -------------------------------------------------------------------------------- /examples/110-wasm/www/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "sourceMap": true, 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": false, 9 | "moduleResolution": "node" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/110-wasm/www/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HTMLWebpackPlugin = require('html-webpack-plugin'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | entry: "./src/bootstrap.ts", 6 | experiments: { 7 | syncWebAssembly: true 8 | }, 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.tsx?$/, 13 | use: 'ts-loader', 14 | exclude: /node_modules/, 15 | }, 16 | { 17 | test: /\.css$/i, 18 | use: ["style-loader", "css-loader"], 19 | }, 20 | ], 21 | }, 22 | resolve: { 23 | extensions: ['.tsx', '.ts', '.js'], 24 | }, 25 | output: { 26 | path: path.resolve(__dirname, "dist"), 27 | filename: "bundle.js", 28 | }, 29 | mode: "development", 30 | plugins: [ 31 | new HTMLWebpackPlugin({ 32 | template: path.resolve(__dirname, 'src/index.html'), 33 | }), 34 | ] 35 | }; 36 | -------------------------------------------------------------------------------- /examples/111-turmrechnen/.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | target 3 | www/node_modules 4 | www/.bin 5 | www/.cache 6 | www/dist 7 | Cargo.lock 8 | www/package-lock.json 9 | -------------------------------------------------------------------------------- /examples/111-turmrechnen/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.diagnostics.disabled": [ 3 | "missing-unsafe" 4 | ] 5 | } -------------------------------------------------------------------------------- /examples/111-turmrechnen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "turmrechnen" 3 | version = "0.1.0" 4 | authors = ["Rainer "] 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | wasm-bindgen = { version = "0.2" } 12 | console_error_panic_hook = { version = "^0.1.0" } 13 | -------------------------------------------------------------------------------- /examples/111-turmrechnen/readme.md: -------------------------------------------------------------------------------- 1 | # Tower Arithmetic 2 | 3 | ## Requirements 4 | 5 | Your task is to write a command-line application in Rust for [*tower calculations*](http://www.floriangeier.at/schule/kopf/kopf.php) (use Google Translate to translate the description into your native language). 6 | 7 | A user passes two parameters to your application via the command line: 8 | 9 | * The starting value (e.g., *9*); the starting value must be > 1. 10 | * The "height" (e.g., *5); the height must be > 2. 11 | 12 | You output the result in the following format: 13 | 14 | ```txt 15 | 9 * 2 = 18 16 | 18 * 3 = 54 17 | 54 * 4 = 216 18 | 216 * 5 = 1080 19 | 1080 / 2 = 540 20 | 540 / 3 = 180 21 | 180 / 4 = 45 22 | 45 / 5 = 9 23 | ``` 24 | 25 | If the user has entered incorrect parameters on the command line or parameters are missing, display an appropriate error message on the screen. 26 | 27 | You can ignore *overflows*. 28 | 29 | ## Tips 30 | 31 | * [Accessing command line parameters](https://doc.rust-lang.org/book/ch12-01-accepting-command-line-arguments.html) 32 | * You can [left-pad output with spaces](https://doc.rust-lang.org/std/fmt/index.html#fillalignment) 33 | * You can convert strings to integers with [`parse`](https://doc.rust-lang.org/std/primitive.str.html#method.parse). 34 | 35 | ## Levels 36 | 37 | Depending on prior knowledge, this task may be more difficult for some and easier for others. Therefore, here are suggestions on how you can solve the example step by step. Each can work through the number of levels that suit them based on their existing programming practice. 38 | 39 | ### Level 1 - Calculation Logic 40 | 41 | * Make assumptions for starting value and height and omit command line parameters. 42 | * Only output the intermediate results. For example: 43 | 44 | ```txt 45 | 9 46 | 18 47 | 54 48 | 216 49 | 1080 50 | 540 51 | 180 52 | 45 53 | 9 54 | ``` 55 | 56 | ### Level 2 - Command Line Parameters 57 | 58 | * Add the ability to pass parameters via the command line. 59 | * Remember to check the parameters with appropriate error messages. 60 | 61 | ### Level 3 - Improved Output 62 | 63 | * Improve the output so that the result looks as requested at the beginning of this description. 64 | * If possible, display the original value right-justified. 65 | 66 | ### Level 4 - Unit Testing 67 | 68 | * Structure your code so that you can test it well. 69 | * Write at least one meaningful unit test ([brief guide](https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html)) 70 | -------------------------------------------------------------------------------- /examples/111-turmrechnen/src/lib.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | 3 | #[wasm_bindgen] 4 | pub struct Tower { 5 | values: Vec, 6 | } 7 | 8 | #[wasm_bindgen] 9 | pub fn init() { 10 | console_error_panic_hook::set_once(); 11 | } 12 | 13 | #[wasm_bindgen] 14 | impl Tower { 15 | pub fn new(base: i32, height: i32) -> Tower { 16 | let mut base = base; 17 | let mut result = Tower { values: vec![0; (height as usize - 1) * 2 * 3], }; 18 | 19 | let mut ix = 0; 20 | for i in 2..=height { 21 | result.values[ix] = base; 22 | result.values[ix + 1] = i; 23 | base *= i; 24 | result.values[ix + 2] = base; 25 | ix += 3; 26 | } 27 | 28 | for i in 2..=height { 29 | result.values[ix] = base; 30 | result.values[ix + 1] = i; 31 | base /= i; 32 | result.values[ix + 2] = base; 33 | ix += 3; 34 | } 35 | 36 | println!("{:?}", result.values); 37 | 38 | result 39 | } 40 | 41 | pub fn tower(&self) -> *const i32 { 42 | self.values.as_ptr() 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | mod tests { 48 | #[test] 49 | fn check_tower() { 50 | let tower = super::Tower::new(1, 3); 51 | assert_eq!(tower.values, vec![1, 2, 2, 2, 3, 6, 6, 2, 3, 3, 3, 1]); 52 | } 53 | } -------------------------------------------------------------------------------- /examples/111-turmrechnen/www/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "turmrechnen-js", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack --config webpack.config.js", 8 | "start": "webpack serve" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "base64-loader": "^1.0.0", 14 | "css-loader": "^6.5.0", 15 | "hello-wasm-pack": "^0.1.0", 16 | "html-webpack-plugin": "^5.5.0", 17 | "style-loader": "^3.3.1", 18 | "ts-loader": "^9.2.6", 19 | "typescript": "^5.1.6", 20 | "webpack": "^5.60.0", 21 | "webpack-cli": "^5.1.4", 22 | "webpack-dev-server": "^4.3.1" 23 | }, 24 | "dependencies": { 25 | "cash-dom": "^8.1.0", 26 | "turmrechnen": "file:../pkg" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/111-turmrechnen/www/src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | // A dependency graph that contains any wasm must all be imported 2 | // asynchronously. This `bootstrap.js` file does the single async import, so 3 | // that no one else needs to worry about it again. 4 | import("./index") 5 | .catch(e => console.error("Error importing `index.js`:", e)); 6 | -------------------------------------------------------------------------------- /examples/111-turmrechnen/www/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 3 | } 4 | 5 | h1 { 6 | font-size: 20px; 7 | } 8 | 9 | p { 10 | font-size: 12px; 11 | } 12 | 13 | table tr td:first-of-type { 14 | text-align: right; 15 | } -------------------------------------------------------------------------------- /examples/111-turmrechnen/www/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Turmrechnen 8 | 9 | 10 |

    Turmrechnen

    11 | 12 |
    13 | 17 | 21 | 22 |
    23 | 24 | 25 | 26 | 27 |
    28 | 29 | -------------------------------------------------------------------------------- /examples/111-turmrechnen/www/src/index.ts: -------------------------------------------------------------------------------- 1 | import "./index.css"; 2 | 3 | import * as turm from "turmrechnen"; 4 | import { memory } from "turmrechnen/turmrechnen_bg.wasm"; 5 | import $ from "cash-dom"; 6 | 7 | $(() => { 8 | turm.init(); 9 | $("#calculate").on("click", () => { 10 | const height = parseInt($("#height").val()); 11 | const tower = turm.Tower.new(parseInt($("#basis").val()), height); 12 | 13 | try { 14 | const values = new Int32Array( 15 | memory.buffer, 16 | tower.tower(), 17 | (height - 1) * 2 * 3 18 | ); 19 | let ix = 0; 20 | let resultTable = $("#result-table"); 21 | for (let i = 0; i < height - 1; i++) { 22 | resultTable.append( 23 | buildTableRow(values[ix++], "*", values[ix++], values[ix++]) 24 | ); 25 | } 26 | for (let i = 0; i < height - 1; i++) { 27 | resultTable.append( 28 | buildTableRow(values[ix++], "/", values[ix++], values[ix++]) 29 | ); 30 | } 31 | } finally { 32 | tower.free(); 33 | } 34 | }); 35 | }); 36 | 37 | function buildTableRow( 38 | basis: number, 39 | op: string, 40 | param: number, 41 | result: number 42 | ) { 43 | return `${basis}${op}${param}=${result}`; 44 | } 45 | -------------------------------------------------------------------------------- /examples/111-turmrechnen/www/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "sourceMap": true, 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": false, 9 | "moduleResolution": "node" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/111-turmrechnen/www/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HTMLWebpackPlugin = require('html-webpack-plugin'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | entry: "./src/bootstrap.ts", 6 | experiments: { 7 | syncWebAssembly: true 8 | }, 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.tsx?$/, 13 | use: 'ts-loader', 14 | exclude: /node_modules/, 15 | }, 16 | { 17 | test: /\.css$/i, 18 | use: ["style-loader", "css-loader"], 19 | }, 20 | ], 21 | }, 22 | resolve: { 23 | extensions: ['.tsx', '.ts', '.js'], 24 | }, 25 | output: { 26 | path: path.resolve(__dirname, "dist"), 27 | filename: "bundle.js", 28 | }, 29 | mode: "development", 30 | plugins: [ 31 | new HTMLWebpackPlugin({ 32 | template: path.resolve(__dirname, 'src/index.html'), 33 | }), 34 | ] 35 | }; 36 | -------------------------------------------------------------------------------- /examples/120-tictactoe/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /examples/120-tictactoe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "tictactoe_logic", 4 | ] -------------------------------------------------------------------------------- /examples/120-tictactoe/readme.md: -------------------------------------------------------------------------------- 1 | # TicTacToe 2 | 3 | This sample is a greatly simplified version of the full [hands-on lab](../../hands-on-labs) of this workshop. It can be used as a demonstration of data modelling in Rust (e.g. structs, traits, generics) during shorter workshops. 4 | -------------------------------------------------------------------------------- /examples/120-tictactoe/rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_small_heuristics = "Max" 2 | -------------------------------------------------------------------------------- /examples/120-tictactoe/tictactoe_logic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tictactoe_logic" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | rstest = "^0" 8 | mockall = "^0" 9 | -------------------------------------------------------------------------------- /examples/120-tictactoe/tictactoe_logic/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Note: Loads the contents of the module square_content from another file 2 | // with the same name as the module. Read more at 3 | // https://doc.rust-lang.org/book/ch07-05-separating-modules-into-different-files.html 4 | mod square_content; 5 | mod board_index; 6 | mod board_content; 7 | mod row; 8 | mod game; 9 | 10 | // Note: Re-exports the content of the square_content module to keep paths short. 11 | // Read more at https://doc.rust-lang.org/reference/items/use-declarations.html#use-visibility 12 | pub use crate::square_content::*; 13 | pub use crate::board_index::*; 14 | pub use crate::board_content::*; 15 | pub use crate::row::*; 16 | pub use crate::game::*; 17 | -------------------------------------------------------------------------------- /examples/200-guess-the-number-game/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /examples/200-guess-the-number-game/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "session-one" 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 | rand = "0.8.4" 10 | -------------------------------------------------------------------------------- /examples/200-guess-the-number-game/readme.md: -------------------------------------------------------------------------------- 1 | # Guess The Number CLI Game 2 | 3 | This is a simple console-based guessing game programmed in Rust. The aim of the game is to guess a randomly generated number within a certain number of attempts, both specified as command-line arguments when starting the game. 4 | 5 | ## Game Logic 6 | 7 | Write a program that consists of the following steps: 8 | 9 | 1. **Creates a random number generator**: It initializes the random number generator which will be used to generate the target number to guess. 10 | 11 | 2. **Handles command-line arguments**: It collects the command-line arguments into a vector of strings. The program expects two arguments: the number of guesses a player has and the maximum possible number that could be guessed. 12 | 13 | 3. **Gameplay loop**: If the correct number of arguments is provided, the game loop begins. The loop runs for the number of guess attempts specified. In each iteration, the player's guess is read from standard input and compared with the random target number. Depending on whether the guess is lower or higher than the target, appropriate messages are printed to the console. If the guess is correct, a success message is printed and the loop breaks. 14 | 15 | ## Tipps 16 | 17 | * Use the `rand` crate for generating random numbers 18 | * Use `std::env` for handling command-line arguments 19 | * Use `std::io` for interacting with the standard input/output streams 20 | 21 | ## Running the Code 22 | 23 | To play the game, compile the program and run it with two numerical command-line arguments. The first argument is the number of guesses you have, and the second argument is the maximum possible number that could be guessed. Example: 24 | 25 | ```sh 26 | cargo run 5 100 27 | ``` 28 | 29 | This command starts the game with 5 guesses, and the number to guess is a random number between 0 and 100. During the game, you'll need to input your guesses via the command line. 30 | 31 | ## Levels 32 | 33 | ### Level 1 - Basics 34 | 35 | * Make assumptions for the input values and omit command line parameters. 36 | 37 | ### Level 2 - Command-line Arguments 38 | 39 | * Add the ability to pass parameters via the command line. 40 | * Remember to check the parameters with appropriate error messages. 41 | 42 | ### Level 3 - Unit Testing 43 | 44 | * Structure your code so that you can test it well. 45 | * Write at least one meaningful unit test ([brief guide](https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html)) 46 | -------------------------------------------------------------------------------- /examples/200-guess-the-number-game/src/main.rs: -------------------------------------------------------------------------------- 1 | use rand::{thread_rng, Rng}; 2 | use std::env; 3 | use std::io; 4 | 5 | // 1. Create a random number 6 | // 2. Read from stdin 7 | // 3. Parse CLI arguments 8 | 9 | fn main() { 10 | let mut rng = thread_rng(); 11 | let args: Vec = env::args().collect(); 12 | 13 | if args.len() == 3 { 14 | let number_of_guesses: u32 = args[1].parse().expect("Could not parse number of tries"); 15 | let number_to_guess = 16 | rng.gen_range(0..=args[2].parse().expect("Could not parse number to guess")); 17 | 18 | for i in 1..=number_of_guesses { 19 | let mut buffer = String::new(); 20 | let _read_bytes = io::stdin() 21 | .read_line(&mut buffer) 22 | .expect("Error reading from CL"); 23 | let guessed_number: u32 = buffer.trim().parse().expect("Could not parse number"); 24 | 25 | let msg = match guessed_number.cmp(&number_to_guess) { 26 | std::cmp::Ordering::Less => "Too low", 27 | std::cmp::Ordering::Equal => "Too high", 28 | std::cmp::Ordering::Greater => { 29 | println!("Just about right after {} steps", i); 30 | break; 31 | } 32 | }; 33 | 34 | println!("{}", msg); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/205-take-n-from-m/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /examples/205-take-n-from-m/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "cfg-if" 7 | version = "1.0.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 10 | 11 | [[package]] 12 | name = "getrandom" 13 | version = "0.2.6" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" 16 | dependencies = [ 17 | "cfg-if", 18 | "libc", 19 | "wasi", 20 | ] 21 | 22 | [[package]] 23 | name = "libc" 24 | version = "0.2.126" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 27 | 28 | [[package]] 29 | name = "ppv-lite86" 30 | version = "0.2.10" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 33 | 34 | [[package]] 35 | name = "rand" 36 | version = "0.8.5" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 39 | dependencies = [ 40 | "libc", 41 | "rand_chacha", 42 | "rand_core", 43 | ] 44 | 45 | [[package]] 46 | name = "rand_chacha" 47 | version = "0.3.1" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 50 | dependencies = [ 51 | "ppv-lite86", 52 | "rand_core", 53 | ] 54 | 55 | [[package]] 56 | name = "rand_core" 57 | version = "0.6.3" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 60 | dependencies = [ 61 | "getrandom", 62 | ] 63 | 64 | [[package]] 65 | name = "random" 66 | version = "0.1.0" 67 | dependencies = [ 68 | "rand", 69 | ] 70 | 71 | [[package]] 72 | name = "wasi" 73 | version = "0.10.2+wasi-snapshot-preview1" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 76 | -------------------------------------------------------------------------------- /examples/205-take-n-from-m/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "random" 3 | version = "0.1.0" 4 | authors = ["stefan.baumgartner "] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | rand = "0.8" 11 | -------------------------------------------------------------------------------- /examples/205-take-n-from-m/README.md: -------------------------------------------------------------------------------- 1 | # take N from M 2 | 3 | Just some fun experiments with Rust, where I try to make my code better and better over time. 4 | -------------------------------------------------------------------------------- /examples/205-take-n-from-m/src/main.rs: -------------------------------------------------------------------------------- 1 | use rand::seq::SliceRandom; 2 | use std::env; 3 | 4 | fn take_n_from_m(take: usize, from: u64) -> Vec { 5 | let mut rng = rand::thread_rng(); 6 | let mut nums: Vec = (1..=from).collect(); 7 | nums.shuffle(&mut rng); 8 | let mut result = nums[0..take].to_vec(); 9 | result.sort_unstable(); 10 | result 11 | } 12 | 13 | fn main() { 14 | let args: Vec = env::args().skip(1).collect(); 15 | 16 | for chunk in args.chunks(2) { 17 | if chunk.len() == 2 { 18 | let take = chunk[0].parse::(); 19 | let from = chunk[1].parse::(); 20 | match (take, from) { 21 | (Ok(t), Ok(f)) => { 22 | println!("take {} from {}", t, f); 23 | println!("-> numbers: {:?}", take_n_from_m(t, f)); 24 | } 25 | (_, _) => { 26 | println!("Error parsing arguments"); 27 | } 28 | } 29 | } else { 30 | println!("Missing arguments, take {} from what?", chunk[0]); 31 | } 32 | } 33 | } 34 | 35 | #[test] 36 | fn test_take_n_from_m() { 37 | let mut result = take_n_from_m(6, 45); 38 | result.sort_by(|a, b| b.partial_cmp(a).unwrap()); 39 | 40 | assert_eq!(result.len(), 6); 41 | assert!(result[0] < 45); 42 | } 43 | -------------------------------------------------------------------------------- /examples/210-clock/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /examples/210-clock/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "clock-example" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /examples/210-clock/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "clock-example" 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 | -------------------------------------------------------------------------------- /examples/210-clock/src/main.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Default)] 2 | struct Clock { 3 | hours: i32, 4 | minutes: i32, 5 | } 6 | 7 | impl Clock { 8 | fn new(hours: i32, minutes: i32) -> Self { 9 | Self { hours, minutes }.normalize() 10 | } 11 | 12 | fn normalize(&mut self) -> Self { 13 | let mut hours = (self.hours + self.minutes / 60) % 24; 14 | let mut minutes = self.minutes % 60; 15 | 16 | if minutes < 0 { 17 | minutes += 60; 18 | hours -= 1; 19 | } 20 | 21 | if hours < 0 { 22 | hours += 24; 23 | } 24 | 25 | Self { hours, minutes } 26 | } 27 | } 28 | 29 | impl std::fmt::Display for Clock { 30 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 31 | write!(f, "{:0>2}:{:0>2}", self.hours, self.minutes) 32 | } 33 | } 34 | 35 | impl std::ops::Add for Clock { 36 | type Output = Clock; 37 | 38 | fn add(self, rhs: Self) -> Self::Output { 39 | Clock::new(self.hours + rhs.hours, self.minutes + rhs.minutes) 40 | } 41 | } 42 | 43 | impl std::ops::Add for Clock { 44 | type Output = Clock; 45 | 46 | fn add(self, rhs: i32) -> Self::Output { 47 | Clock::new(self.hours, self.minutes + rhs) 48 | } 49 | } 50 | 51 | impl From for Clock { 52 | fn from(val: i32) -> Clock { 53 | Clock::new(0, val) 54 | } 55 | } 56 | 57 | fn main() { 58 | let clock = Clock::new(14, 55); 59 | let clock_2 = Clock::new(34, 155); 60 | let default_clock: Clock = Default::default(); 61 | 62 | let clock = clock + clock_2; 63 | 64 | println!("{}", clock); 65 | 66 | let clock = default_clock + 1055; 67 | 68 | println!("{}", clock); 69 | 70 | let clock: Clock = 2055.into(); 71 | 72 | println!("{}", clock); 73 | } 74 | -------------------------------------------------------------------------------- /examples/220-fibonacci-iterator/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /examples/220-fibonacci-iterator/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "fibonacci-iterator" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /examples/220-fibonacci-iterator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fibonacci-iterator" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /examples/220-fibonacci-iterator/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::iter; 2 | 3 | struct Fibonacci { 4 | curr: u128, 5 | next: u128, 6 | } 7 | 8 | // Implement iterator for our Fibonacci struct 9 | impl Iterator for Fibonacci { 10 | type Item = u128; 11 | 12 | fn next(&mut self) -> Option { 13 | // Note that checked_add returns None if the addition overflows. 14 | // That will end our iterator. 15 | let new_next = self.curr.checked_add(self.next)?; 16 | self.curr = self.next; 17 | self.next = new_next; 18 | Some(self.curr) 19 | } 20 | } 21 | 22 | fn main() { 23 | // Use Fibonacci struct as an iterator 24 | let fibb = Fibonacci { curr: 0, next: 1 }; 25 | for (idx, i) in fibb.enumerate() { 26 | println!("{}: {}", idx, i) 27 | } 28 | 29 | // Build iterator using from_fn and closure 30 | let mut fibb_state = (0u128, 1u128); 31 | let fibb = iter::from_fn(move || { 32 | let new_next = fibb_state.0.checked_add(fibb_state.1)?; 33 | fibb_state.0 = fibb_state.1; 34 | fibb_state.1 = new_next; 35 | Some(fibb_state.0) 36 | }); 37 | for (idx, i) in fibb.enumerate() { 38 | println!("{}: {}", idx, i) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/230-progress-iterator/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /examples/230-progress-iterator/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "progress-iterator" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /examples/230-progress-iterator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "progress-iterator" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /examples/230-progress-iterator/README.md: -------------------------------------------------------------------------------- 1 | # Progress Iterator example 2 | 3 | Based on [Type-Driven API Design in Rust](https://www.youtube.com/watch?v=bnnacleqg6k) by Will Chrichton 4 | 5 | This example implements: 6 | - Progress struct that wraps the iterator 7 | - `Iterator` for Progress to print the progress and forward the wrapped iterator's elements --> **Iterator** 8 | - `ProgressIteratorExt`, so `Progress` is attached to each iterator who calles `.progress()` --> **Extension Traits** 9 | - `Unbounded` and `Bounded` state structs 10 | - `ProgressDisplay` to decide how `Bounded` and `Unbounded` should be displayed --> **Typestate** 11 | - `Progress` starts `Unbounded` 12 | - `.with_bounds` for all `Unbounded` to transform state into `Bounded` 13 | - `.with_delims` for all `Bounded` to change delimiters 14 | -------------------------------------------------------------------------------- /examples/240-multi-threading-example/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /examples/240-multi-threading-example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "multi-threading" 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 | rand = "0.8.4" 10 | -------------------------------------------------------------------------------- /examples/240-multi-threading-example/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | rc::Rc, 3 | sync::{Arc, Mutex}, 4 | thread, 5 | }; 6 | 7 | use rand::{prelude::IteratorRandom, thread_rng}; 8 | 9 | #[derive(Default, Debug)] 10 | struct Lotto { 11 | numbers: Vec, 12 | } 13 | 14 | impl Lotto { 15 | pub fn new(amount: usize, max: usize) -> Self { 16 | let pot = 1..=max; 17 | let mut rng = thread_rng(); 18 | Self { 19 | numbers: pot.choose_multiple(&mut rng, amount), 20 | } 21 | } 22 | } 23 | 24 | impl IntoIterator for Lotto { 25 | type Item = as IntoIterator>::Item; 26 | type IntoIter = as IntoIterator>::IntoIter; 27 | 28 | fn into_iter(self) -> Self::IntoIter { 29 | self.numbers.into_iter() 30 | } 31 | } 32 | 33 | fn main() { 34 | let mut handles = Vec::new(); 35 | let mutex = Arc::new(Mutex::new(3)); 36 | let z = Rc::new(2); 37 | 38 | let pairs = [(6, 45), (5, 50), (2, 12)]; 39 | for (take, from) in pairs { 40 | let mutex = mutex.clone(); 41 | let handle = thread::spawn(move || { 42 | let res = match mutex.lock() { 43 | Ok(res) => res.to_owned(), 44 | Err(_) => 0, 45 | }; 46 | if take == 5 { 47 | panic!("at the disco"); 48 | } 49 | Lotto::new(take, from) 50 | }); 51 | handles.push(handle); 52 | } 53 | 54 | for handle in handles { 55 | if let Ok(result) = handle.join() { 56 | println!("{:?}", result); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /examples/245-async-tokio/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /examples/245-async-tokio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-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 | tokio = { version = "1", features = ["full"] } 10 | 11 | -------------------------------------------------------------------------------- /examples/246-fun-with-futures/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /examples/246-fun-with-futures/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fun_with_futures" 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 | tokio = { version = "1", features = ["full"] } 10 | futures = "0" 11 | -------------------------------------------------------------------------------- /examples/246-fun-with-futures/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{time::{Duration, Instant}, thread, arch::asm}; 2 | 3 | use futures::future::join_all; 4 | use tokio::time::sleep; 5 | 6 | async fn do_stuff_in_background() { 7 | // Spawn 100 background tasks 8 | let mut tasks = Vec::new(); 9 | for i in 0..100 { 10 | let task = tokio::spawn(async move { 11 | // Note how i is moved into the closure 12 | println!("Starting iteration {i}"); 13 | 14 | // Simulate some long-running I/O operation. 15 | // Try chaning from tokio's sleep to our own 16 | // spin_wait_for_a_second function that blocks the CPU. 17 | sleep(Duration::from_secs(1)).await; 18 | //spin_wait_for_a_second(); 19 | 20 | // Print notification that we're done. For closer inspection, 21 | // we also print the thread ID. 22 | let thread_id = thread::current().id(); 23 | println!("Done iteration {i} in Thread {thread_id:?}"); 24 | 25 | // Uncomment this to see what happens if a task panics 26 | // if i % 10 == 0 { 27 | // panic!("Panic in iteration {i}"); 28 | // } 29 | 30 | // Return the result of the computation. Can be anything, 31 | // here we just return the square of the input. 32 | i * i 33 | }); 34 | tasks.push(task); 35 | 36 | // Uncomment this to see what happens if we wait just a tiny bit 37 | // between spawning the tasks. Take special note of the thread IDs. 38 | //sleep(Duration::from_millis(5)).await; 39 | } 40 | 41 | // Wait for all tasks to finish 42 | let results = join_all(tasks).await; 43 | 44 | // Print the results 45 | for (i, result) in results.iter().enumerate() { 46 | match result { 47 | Ok(val) => println!("Result of task {i} is {val}"), 48 | Err(e) => println!("Task panicked with error: {e:?}"), 49 | } 50 | } 51 | } 52 | 53 | fn spin_wait_for_a_second() { 54 | let start = Instant::now(); 55 | let wait_time = Duration::from_secs(1); 56 | 57 | while Instant::now() - start < wait_time { 58 | // Compiler will not optimize away this loop 59 | // because of the asm! call. 60 | unsafe { asm!("nop"); } 61 | } 62 | } 63 | 64 | #[tokio::main] 65 | async fn main() { 66 | do_stuff_in_background().await; 67 | } 68 | -------------------------------------------------------------------------------- /examples/250-trait-objects/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /examples/250-trait-objects/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trait_objects" 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 | chrono = "0.4" 10 | -------------------------------------------------------------------------------- /examples/250-trait-objects/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | 3 | use chrono::{Utc, Datelike}; 4 | 5 | trait Animal { 6 | fn is_mammal(&self) -> bool; 7 | fn get_species(&self) -> String; 8 | fn get_sound(&self) -> String; 9 | fn number_of_legs(&self) -> i32; 10 | } 11 | 12 | struct Cat { } 13 | 14 | struct Spider { } 15 | 16 | impl Animal for Cat { 17 | fn is_mammal(&self) -> bool { 18 | true 19 | } 20 | 21 | fn get_species(&self) -> String { 22 | "Cat".to_string() 23 | } 24 | 25 | fn get_sound(&self) -> String { 26 | "Meow".to_string() 27 | } 28 | 29 | fn number_of_legs(&self) -> i32 { 30 | 4 31 | } 32 | } 33 | 34 | impl Display for Cat { 35 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 36 | write!(f, "{}", self.get_species()) 37 | } 38 | } 39 | 40 | impl Animal for Spider { 41 | fn is_mammal(&self) -> bool { 42 | false 43 | } 44 | 45 | fn get_species(&self) -> String { 46 | "Spider".to_string() 47 | } 48 | 49 | fn get_sound(&self) -> String { 50 | "Hiss".to_string() 51 | } 52 | 53 | fn number_of_legs(&self) -> i32 { 54 | 8 55 | } 56 | } 57 | 58 | fn get_a_cat() -> impl Animal { 59 | Cat { } 60 | } 61 | 62 | fn get_an_animal() -> Box { 63 | if Utc::now().naive_utc().day() % 2 == 0 { Box::new(Spider {}) } else { Box::new(Cat {}) } 64 | } 65 | 66 | fn do_something_with_animal(animal: &impl Animal) { 67 | println!("{}", animal.get_species()); 68 | } 69 | 70 | fn display_animal(animal: &T) { 71 | println!("{}", animal); 72 | } 73 | 74 | fn main() { 75 | let animals: Vec> = vec![ 76 | Box::new(Cat {}), 77 | Box::new(Spider {}), 78 | ]; 79 | 80 | for animal in animals { 81 | println!("{}", animal.get_species()); 82 | } 83 | 84 | let cat = get_a_cat(); 85 | do_something_with_animal(&cat); 86 | do_something_with_animal(&Cat {}); 87 | do_something_with_animal(&Spider {}); 88 | 89 | let animal = get_an_animal(); 90 | println!("{}", animal.get_species()); 91 | 92 | display_animal(&Cat {}); 93 | // display_animal(&Spider {}); // -> doesn't work 94 | } 95 | -------------------------------------------------------------------------------- /examples/255-traits-testing/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /examples/255-traits-testing/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "traitstesting" 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 | 10 | [dev-dependencies] 11 | mockall = "0.12.0" 12 | -------------------------------------------------------------------------------- /examples/255-traits-testing/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | #[cfg(test)] 4 | use mockall::automock; 5 | 6 | struct Customer { 7 | id: usize, 8 | name: String, 9 | } 10 | 11 | #[cfg_attr(test, automock)] 12 | trait CustomerRepository { 13 | fn get(&self, key: usize) -> Option; 14 | } 15 | 16 | struct CustomerDatabaseRepository {} 17 | 18 | impl CustomerRepository for CustomerDatabaseRepository { 19 | fn get(&self, key: usize) -> Option { 20 | // Here we would implement the DB logic to retrieve a customer 21 | match key { 22 | 0 => Some(Customer { 23 | id: key, 24 | name: "John Doe".to_string(), 25 | }), 26 | _ => None, 27 | } 28 | } 29 | } 30 | 31 | struct BusinessLogic<'a> { 32 | customer_repository: &'a dyn CustomerRepository, 33 | } 34 | 35 | impl<'a> BusinessLogic<'a> { 36 | fn count_johns(&self) -> usize { 37 | let mut count = 0; 38 | for i in 0..10 { 39 | let customer = self.customer_repository.get(i); 40 | if customer.is_some() && customer.unwrap().name == "John Doe" { 41 | count += 1; 42 | } 43 | } 44 | count 45 | } 46 | } 47 | 48 | #[cfg(test)] 49 | mod tests { 50 | use mockall::predicate::{gt, lt}; 51 | 52 | use super::*; 53 | 54 | #[test] 55 | fn count_johns() { 56 | let mut repo_mock = MockCustomerRepository::new(); 57 | repo_mock.expect_get() 58 | .with(lt(2)) 59 | .returning(|_| Some(Customer { 60 | id: 0, 61 | name: "John Doe".to_string(), 62 | })); 63 | repo_mock.expect_get() 64 | .with(gt(0)) 65 | .returning(|_| None); 66 | 67 | let business_logic = BusinessLogic { customer_repository: &repo_mock }; 68 | assert_eq!(business_logic.count_johns(), 2); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/300-hello-diesel/.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL=./mydb.db 2 | -------------------------------------------------------------------------------- /examples/300-hello-diesel/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | *.db -------------------------------------------------------------------------------- /examples/300-hello-diesel/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello-diesel" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | diesel = { version = "^2.0.0", features = ["sqlite", "r2d2"] } 8 | dotenvy = "^0.15.5" 9 | -------------------------------------------------------------------------------- /examples/300-hello-diesel/diesel.toml: -------------------------------------------------------------------------------- 1 | # For documentation on how to configure this file, 2 | # see https://diesel.rs/guides/configuring-diesel-cli 3 | 4 | [print_schema] 5 | file = "src/schema.rs" 6 | 7 | [migrations_directory] 8 | dir = "migrations" 9 | -------------------------------------------------------------------------------- /examples/300-hello-diesel/migrations/2022-09-25-081228_create_hero_types/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE heroType 2 | -------------------------------------------------------------------------------- /examples/300-hello-diesel/migrations/2022-09-25-081228_create_hero_types/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE hero_types ( 2 | id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 3 | name TEXT NOT NULL 4 | ) 5 | -------------------------------------------------------------------------------- /examples/300-hello-diesel/migrations/2022-09-25-081642_create_hero/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE hero -------------------------------------------------------------------------------- /examples/300-hello-diesel/migrations/2022-09-25-081642_create_hero/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE heroes ( 2 | id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 3 | name TEXT NOT NULL, 4 | hero_type_id INTEGER NOT NULL, 5 | FOREIGN KEY(hero_type_id) REFERENCES hero_types(id) 6 | ) 7 | -------------------------------------------------------------------------------- /examples/300-hello-diesel/mydb.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-linz/rust-fundamentals-training/9033deff4103b96bbc0f0749a9eb4fbaed0a1825/examples/300-hello-diesel/mydb.db -------------------------------------------------------------------------------- /examples/300-hello-diesel/readme.md: -------------------------------------------------------------------------------- 1 | # Hello Diesel 2 | 3 | ## Preparation 4 | 5 | * Install SQLite client libraries: 6 | * `sudo apt-get install libsqlite3-dev` 7 | * Install Diesel CLI 8 | * `cargo install diesel_cli --no-default-features --features sqlite` 9 | * Set environment variable for Diesel CLI (and/or use *.env*) 10 | * `export DATABASE_URL=./mydb.db` 11 | 12 | ## Create DB Structure 13 | 14 | ```txt 15 | diesel setup 16 | diesel migration generate create_hero_types 17 | diesel migration generate create_hero 18 | diesel migration run 19 | ``` 20 | 21 | * Consider [*diesel_migrations*](https://docs.rs/crate/diesel_migrations/2.0.0) crate for production 22 | 23 | -------------------------------------------------------------------------------- /examples/300-hello-diesel/rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 130 -------------------------------------------------------------------------------- /examples/300-hello-diesel/src/models.rs: -------------------------------------------------------------------------------- 1 | use diesel::prelude::*; 2 | use crate::schema::{hero_types, heroes}; 3 | 4 | #[derive(Queryable, Identifiable)] 5 | pub struct HeroType { 6 | pub id: i32, 7 | pub name: String, 8 | } 9 | 10 | #[derive(Insertable)] 11 | #[diesel(table_name = hero_types)] 12 | pub struct NewHeroType { 13 | pub name: String, 14 | } 15 | 16 | impl NewHeroType { 17 | pub fn new(name: String) -> Self { 18 | Self { name } 19 | } 20 | } 21 | 22 | #[derive(Queryable, Identifiable, Associations)] 23 | #[diesel(belongs_to(HeroType))] 24 | #[diesel(table_name = heroes)] 25 | pub struct Hero { 26 | pub id: i32, 27 | pub name: String, 28 | pub hero_type_id: i32, 29 | } 30 | 31 | #[derive(Insertable)] 32 | #[diesel(table_name = heroes)] 33 | pub struct NewHero { 34 | pub name: String, 35 | pub hero_type_id: i32, 36 | } 37 | 38 | impl NewHero { 39 | pub fn new(name: String, hero_type_id: i32) -> Self { 40 | Self { name, hero_type_id } 41 | } 42 | } -------------------------------------------------------------------------------- /examples/300-hello-diesel/src/schema.rs: -------------------------------------------------------------------------------- 1 | // @generated automatically by Diesel CLI. 2 | 3 | diesel::table! { 4 | hero_types (id) { 5 | id -> Integer, 6 | name -> Text, 7 | } 8 | } 9 | 10 | diesel::table! { 11 | heroes (id) { 12 | id -> Integer, 13 | name -> Text, 14 | hero_type_id -> Integer, 15 | } 16 | } 17 | 18 | diesel::joinable!(heroes -> hero_types (hero_type_id)); 19 | 20 | diesel::allow_tables_to_appear_in_same_query!( 21 | hero_types, 22 | heroes, 23 | ); 24 | -------------------------------------------------------------------------------- /examples/400-hello-axum/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /examples/400-hello-axum/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello-axum" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tokio = { version = "1", features = ["full"] } 8 | axum = { version = "^0.5", features = ["json"] } 9 | axum-extra = { version = "^0.3", features = ["cookie"] } 10 | serde = { version = "^1.0", features = ["derive"] } 11 | base64 = "0.13" 12 | -------------------------------------------------------------------------------- /examples/400-hello-axum/requests.http: -------------------------------------------------------------------------------- 1 | @host = http://127.0.0.1:3000 2 | 3 | ### 4 | GET {{host}} 5 | 6 | ### 7 | GET {{host}}/protected 8 | 9 | ### 10 | GET {{host}}/protected 11 | x-api-key: c2VjcmV0 12 | 13 | ### 14 | GET {{host}}/protected 15 | x-api-key: c2VjcmV 16 | 17 | ### 18 | # @name newHero 19 | POST {{host}}/heroes 20 | Content-Type: application/json 21 | 22 | { 23 | "name": "Homelander", 24 | "canFly": true 25 | } 26 | 27 | ### 28 | GET {{host}}/heroes 29 | 30 | ### 31 | @addedHeroId={{newHero.response.body.$.id}} 32 | GET {{host}}/heroes/{{addedHeroId}} 33 | -------------------------------------------------------------------------------- /examples/400-hello-axum/src/api.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::{atomic::{AtomicUsize, Ordering}, RwLock, Arc}, collections::HashMap}; 2 | 3 | use axum::{Json, Extension, http::StatusCode, response::IntoResponse, extract::Path}; 4 | use serde::{Serialize, Deserialize}; 5 | 6 | pub type ID = usize; 7 | 8 | // Rocket uses Serde for serializing/deserializing data. 9 | #[derive(Serialize, Debug, Clone)] 10 | pub struct Hero { 11 | pub id: ID, 12 | pub name: String, 13 | #[serde(rename(serialize = "canFly"))] 14 | pub can_fly: bool, 15 | } 16 | 17 | #[derive(Deserialize, Debug)] 18 | pub struct NewHero { 19 | pub name: String, 20 | #[serde(rename(deserialize = "canFly"))] 21 | pub can_fly: bool, 22 | } 23 | 24 | pub struct HeroCount(AtomicUsize); 25 | 26 | impl HeroCount { 27 | pub fn new() -> Self { 28 | Self(AtomicUsize::new(1)) 29 | } 30 | } 31 | 32 | pub struct HeroesMap(RwLock>); 33 | 34 | impl HeroesMap { 35 | pub fn new() -> Self { 36 | Self { 0: RwLock::new(HashMap::new()) } 37 | } 38 | } 39 | 40 | pub async fn add_hero(Json(hero): Json, Extension(heroes): Extension>, Extension(hero_count): Extension>) -> impl IntoResponse { 41 | let hid = hero_count.0.fetch_add(1, Ordering::Relaxed); 42 | 43 | // Build new hero 44 | let new_hero = Hero { 45 | id: hid, 46 | name: hero.name, 47 | can_fly: hero.can_fly, 48 | }; 49 | 50 | // Insert new hero in hashmap 51 | let mut heroes = heroes.0.write().unwrap(); 52 | heroes.insert(hid, new_hero.clone()); 53 | 54 | (StatusCode::CREATED, Json(new_hero)).into_response() 55 | } 56 | 57 | pub async fn get_all(Extension(heroes): Extension>) -> Json> { 58 | Json(heroes.0.read().unwrap().values().cloned().collect()) 59 | } 60 | 61 | pub async fn get_hero(Path(id): Path, Extension(heroes): Extension>) -> impl IntoResponse { 62 | let heroes = heroes.0.read().unwrap(); 63 | if let Some(hero) = heroes.get(&id) { 64 | Json(hero).into_response() 65 | } else { 66 | StatusCode::NOT_FOUND.into_response() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/400-hello-axum/src/api_keys.rs: -------------------------------------------------------------------------------- 1 | use axum::{extract::{FromRequest, RequestParts}, http, async_trait}; 2 | 3 | #[derive(Debug)] 4 | pub struct ApiKey(pub String); 5 | 6 | #[async_trait] 7 | impl FromRequest for ApiKey where B: Send { 8 | type Rejection = (http::StatusCode, &'static str); 9 | 10 | async fn from_request(req: &mut RequestParts) -> Result { 11 | if let Some(header) = req.headers().get("X-API-KEY") { 12 | let header_str = header.to_str().map_err(|_| (http::StatusCode::UNAUTHORIZED, "Invalid API key"))?; 13 | let api_key_utf8 = base64::decode(header_str).map_err(|_| (http::StatusCode::UNAUTHORIZED, "Invalid API key"))?; 14 | let api_key = String::from_utf8(api_key_utf8).map_err(|_| (http::StatusCode::UNAUTHORIZED, "Invalid API key"))?; 15 | Ok(ApiKey(api_key)) 16 | } else { 17 | Err((http::StatusCode::UNAUTHORIZED, "Missing API key header")) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/450-request/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /examples/450-request/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "request" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /examples/450-request/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "request" 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 | -------------------------------------------------------------------------------- /examples/450-request/src/fibonacci.rs: -------------------------------------------------------------------------------- 1 | pub struct Fibonacci { 2 | curr: u128, 3 | next: u128, 4 | } 5 | 6 | impl Iterator for Fibonacci { 7 | type Item = u128; // Associated Type 8 | 9 | fn next(&mut self) -> Option { 10 | let new_next = self.curr.checked_add(self.next)?; 11 | self.curr = self.next; 12 | self.next = new_next; 13 | Some(self.curr) 14 | } 15 | } 16 | 17 | impl Default for Fibonacci { 18 | fn default() -> Self { 19 | Self { curr: 0, next: 1 } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/500-error-handling/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /examples/500-error-handling/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "error-handling" 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 | tokio = { version = "1.0", features = ["full"] } 10 | anyhow = "1.0" 11 | thiserror = "1.0" 12 | serde = { version = "1.0", features = ["derive"] } 13 | serde_json = "1.0" -------------------------------------------------------------------------------- /examples/500-error-handling/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "connectionString": "Something" 3 | } -------------------------------------------------------------------------------- /examples/600-echo-server-tokio/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /examples/600-echo-server-tokio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tokio-async" 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 | tokio = { version = "1.23.0", features = ["full"] } 10 | -------------------------------------------------------------------------------- /examples/600-echo-server-tokio/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::Result; 2 | 3 | use tokio::{ 4 | io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, 5 | net::{TcpListener, TcpStream}, 6 | }; 7 | 8 | async fn echo(mut socket: TcpStream) -> Result<()> { 9 | let (reader, mut writer) = socket.split(); 10 | let mut reader = BufReader::new(reader); 11 | 12 | loop { 13 | let mut buffer = String::new(); 14 | reader.read_line(&mut buffer).await?; 15 | if buffer.trim() == "quit" { 16 | break; 17 | } 18 | writer.write_all(buffer.as_bytes()).await?; 19 | } 20 | Ok(()) 21 | } 22 | 23 | #[tokio::main(flavor = "current_thread")] 24 | async fn main() -> Result<()> { 25 | let listener = TcpListener::bind("localhost:8082").await?; 26 | loop { 27 | if let Ok((socket, addr)) = listener.accept().await { 28 | println!("Listening to {}", addr); 29 | tokio::task::spawn(echo(socket)); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/700-c-interop/area_calculator_c/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.a 3 | -------------------------------------------------------------------------------- /examples/700-c-interop/area_calculator_c/Makefile: -------------------------------------------------------------------------------- 1 | libareacalc.a: area_calculator.o 2 | ar rcs libareacalc.a area_calculator.o 3 | 4 | area_calculator.o: area_calculator.c 5 | gcc -c area_calculator.c -o area_calculator.o 6 | 7 | clean: 8 | rm -f *.o *.a 9 | -------------------------------------------------------------------------------- /examples/700-c-interop/area_calculator_c/area_calculator.c: -------------------------------------------------------------------------------- 1 | #include "area_calculator.h" 2 | #include 3 | 4 | void area_ellipse( 5 | ellipse_t *ellipse, 6 | double *area 7 | ) { 8 | *area = ellipse->a * ellipse->b * M_PI; 9 | } 10 | -------------------------------------------------------------------------------- /examples/700-c-interop/area_calculator_c/area_calculator.h: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | double a; 3 | double b; 4 | } ellipse_t; 5 | 6 | void area_ellipse( 7 | ellipse_t *ellipse, 8 | double *area 9 | ); 10 | -------------------------------------------------------------------------------- /examples/700-c-interop/area_calculator_rs/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /examples/700-c-interop/area_calculator_rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "area_calculator_rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | cty = "0" 8 | -------------------------------------------------------------------------------- /examples/700-c-interop/area_calculator_rs/build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | fn main() { 4 | Command::new("make") 5 | .current_dir("../area_calculator_c") 6 | .status() 7 | .expect("failed to execute process"); 8 | 9 | println!(r"cargo:rustc-link-search=all=../area_calculator_c"); 10 | println!("cargo:rustc-link-lib=areacalc"); 11 | } 12 | -------------------------------------------------------------------------------- /examples/700-c-interop/area_calculator_rs/src/main.rs: -------------------------------------------------------------------------------- 1 | #[repr(C)] 2 | pub struct Ellipse { 3 | pub a: cty::c_double, 4 | pub b: cty::c_double, 5 | } 6 | 7 | extern "C" { pub fn area_ellipse(ellipse: *mut Ellipse, area: *mut f64); } 8 | 9 | fn main() { 10 | let mut ellipse = Ellipse { a: 3.0, b: 4.0 }; 11 | let mut area = 0.0; 12 | unsafe { area_ellipse(&mut ellipse, &mut area) }; 13 | println!("area = {}", area); 14 | } 15 | -------------------------------------------------------------------------------- /examples/701-c-interop_2/area_calculation_c/area_calculation_rs.h: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | double a; 3 | double b; 4 | } ellipse_t; 5 | 6 | double area_ellipse(ellipse_t *e); 7 | -------------------------------------------------------------------------------- /examples/701-c-interop_2/area_calculation_c/justfile: -------------------------------------------------------------------------------- 1 | build: 2 | cd ../area_calculation_rs && cargo build 3 | gcc main.c -o main -larea_calculation_rs -L../area_calculation_rs/target/debug 4 | 5 | run: build 6 | ./main 7 | -------------------------------------------------------------------------------- /examples/701-c-interop_2/area_calculation_c/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "area_calculation_rs.h" 3 | 4 | void main() { 5 | ellipse_t ellipse = { 10.0, 20.0 }; 6 | double area = area_ellipse(&ellipse); 7 | printf("Area of ellipse: %f\n", area); 8 | } -------------------------------------------------------------------------------- /examples/701-c-interop_2/area_calculation_rs/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /examples/701-c-interop_2/area_calculation_rs/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "area_calculation_rs" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "cty", 10 | ] 11 | 12 | [[package]] 13 | name = "cty" 14 | version = "0.2.2" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" 17 | -------------------------------------------------------------------------------- /examples/701-c-interop_2/area_calculation_rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "area_calculation_rs" 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 | [lib] 9 | crate-type = ["staticlib"] 10 | 11 | [dependencies] 12 | cty = "0" -------------------------------------------------------------------------------- /examples/701-c-interop_2/area_calculation_rs/cbindgen.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-linz/rust-fundamentals-training/9033deff4103b96bbc0f0749a9eb4fbaed0a1825/examples/701-c-interop_2/area_calculation_rs/cbindgen.toml -------------------------------------------------------------------------------- /examples/701-c-interop_2/area_calculation_rs/justfile: -------------------------------------------------------------------------------- 1 | generate_h: 2 | cbindgen --config cbindgen.toml --crate area_calculation_rs --output my_header.h --lang c 3 | 4 | -------------------------------------------------------------------------------- /examples/701-c-interop_2/area_calculation_rs/my_header.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | typedef struct Ellipse { 7 | double a; 8 | double b; 9 | } Ellipse; 10 | 11 | double area_ellipse(const struct Ellipse *ellipse); 12 | -------------------------------------------------------------------------------- /examples/701-c-interop_2/area_calculation_rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[repr(C)] 2 | pub struct Ellipse { 3 | pub a: cty::c_double, 4 | pub b: cty::c_double, 5 | } 6 | 7 | #[no_mangle] 8 | pub extern "C" fn area_ellipse(ellipse: &Ellipse) -> cty::c_double { 9 | ellipse.a * ellipse.b * std::f64::consts::PI 10 | } 11 | -------------------------------------------------------------------------------- /examples/710-bus-graph/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /examples/710-bus-graph/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bus_graph" 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 | -------------------------------------------------------------------------------- /examples/720-deref/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /examples/720-deref/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "deref" 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 | -------------------------------------------------------------------------------- /examples/720-deref/src/main.rs: -------------------------------------------------------------------------------- 1 | struct CreditCardNumber { 2 | number: String, 3 | } 4 | 5 | impl CreditCardNumber { 6 | fn new(number: &str) -> CreditCardNumber { 7 | CreditCardNumber { 8 | number: number.to_string(), 9 | } 10 | } 11 | 12 | fn is_valid(&self) -> bool { 13 | // TODO: Implement Luhn algorithm; not done here because not relevant for sample. 14 | true 15 | } 16 | } 17 | 18 | impl std::ops::Deref for CreditCardNumber { 19 | type Target = str; 20 | 21 | fn deref(&self) -> &str { 22 | &self.number 23 | } 24 | } 25 | 26 | fn main() { 27 | let card = &CreditCardNumber::new("4012888888881881"); 28 | println!("The card number has a length of {} and is valid: {}", 29 | // Note that len is a method on str, not on CreditCardNumber. Accessible because of Deref. 30 | card.len(), 31 | // Note that is_valid is a method on CreditCardNumber. 32 | card.is_valid()); 33 | } 34 | -------------------------------------------------------------------------------- /examples/730-demystifying-iterators/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | # RustRover 17 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 18 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 19 | # and can be added to the global gitignore or merged into this file. For a more nuclear 20 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 21 | #.idea/ -------------------------------------------------------------------------------- /examples/730-demystifying-iterators/generators/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generators" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /examples/730-demystifying-iterators/generators/src/main.rs: -------------------------------------------------------------------------------- 1 | /// Iterator generating fibonacci numbers 2 | struct Fibonacci { 3 | curr: u64, 4 | next: u64, 5 | } 6 | 7 | impl Fibonacci { 8 | fn new() -> Self { 9 | Fibonacci { curr: 0, next: 1 } 10 | } 11 | } 12 | 13 | impl Iterator for Fibonacci { 14 | type Item = u64; 15 | 16 | fn next(&mut self) -> Option { 17 | let old_curr = self.curr; 18 | self.curr = self.next; 19 | self.next = old_curr + self.next; 20 | Some(old_curr) 21 | } 22 | } 23 | 24 | fn main() { 25 | let fib = Fibonacci::new(); 26 | for i in fib.take(10) { 27 | println!("{i}"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/730-demystifying-iterators/iterator_tools/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "iterator_tools" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | num-traits = "0" -------------------------------------------------------------------------------- /examples/730-demystifying-iterators/iterator_tools/src/main.rs: -------------------------------------------------------------------------------- 1 | /// An iterator that finds palindromes in a sequence of elements. 2 | struct Palindrome 3 | where 4 | I: Iterator, 5 | { 6 | iter: I, 7 | } 8 | 9 | /// Implement the Iterator trait for the Palindrome struct. 10 | impl Iterator for Palindrome 11 | where 12 | I: Iterator, 13 | I::Item: ToString, 14 | { 15 | type Item = I::Item; 16 | 17 | fn next(&mut self) -> Option { 18 | // Find the next palindrome in the sequence. 19 | self.iter.find(|item| { 20 | let string = item.to_string(); 21 | let reversed = string.chars().rev().collect::(); 22 | string == reversed 23 | }) 24 | } 25 | } 26 | 27 | /// A trait that extends the Iterator trait with a method to find palindromes. 28 | trait PalindromeIterator: Iterator { 29 | fn palindromes(self) -> Palindrome 30 | where 31 | Self: Sized, 32 | Self::Item: ToString, 33 | { 34 | Palindrome { iter: self } 35 | } 36 | } 37 | 38 | impl PalindromeIterator for I {} 39 | 40 | fn main() { 41 | for palindrome in (0..1000u32).palindromes() { 42 | println!("{}", palindrome); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/730-demystifying-iterators/iterators_from_scratch/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "iterators_from_scratch" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /hands-on-labs/010-getting-started/readme.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Cargo Workspace 4 | 5 | Start with an empty folder. 6 | 7 | We are going to put all our Rust packages in a common [*Cargo Workspace*](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html). For that, create a [*Cargo.toml*](https://doc.rust-lang.org/cargo/reference/manifest.html) manifest file. Copy the content from [Cargo.toml](../999-final/Cargo.toml). Obviously, this is how the file will look like at the end of our hands-on-lab. You have to start with an empty `members`-list and add the folder names of the packages we are going to create step by step. 8 | 9 | ## Game Logic 10 | 11 | Create a new library using Cargo: `cargo new --lib battleship_game_logic`. Add the new library to the previously created workspace. 12 | 13 | Use `cargo build` to verify that your project compiles. 14 | 15 | ## Dependencies 16 | 17 | Copy the dependencies into the library's *Cargo.toml* from [Cargo.toml](../999-final/battleship_game_logic/Cargo.toml). Lookup all the referenced crates on [crates.io](https://crates.io/) and find out what they are used for. You do not need to understand all the details. Overview knowledge is sufficient. 18 | 19 | **Tips:** 20 | 21 | * Make yourself familiar with [how to specify dependencies](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html). 22 | * Make yourself familiar with [Cargo dependency features](https://doc.rust-lang.org/cargo/reference/features.html#dependency-features). 23 | * You might ask yourself why we reference *getrandom* with the feature *js*. The reason is that we need WASM support for a later sample ([read more](https://docs.rs/getrandom/0.2.3/getrandom/#webassembly-support)). 24 | -------------------------------------------------------------------------------- /hands-on-labs/020-square-content/readme.md: -------------------------------------------------------------------------------- 1 | # Square Content 2 | 3 | ## Introduction 4 | 5 | The classical Battleship game is played on boards with 10x10=100 squares. Our first task is to create a data structure representing the content of a single square. 6 | 7 | ## *square_content.rs* 8 | 9 | Add the file *square_content.rs* to the *battleship_game_logic* package. Copy the content from [square_content.rs](../999-final/battleship_game_logic/src/square_content.rs) or implement it yourself based on the included unit tests. Make yourself familiar with the code and/or discuss it with your Rust trainers. Note the recommended reading links at the beginning of the file. 10 | 11 | ## *lib.rs* 12 | 13 | In our sample, *lib.rs* will not contain any logic. It will just re-export public declarations from other modules. Take a look at [lib.rs](../999-final/battleship_game_logic/src/lib.rs) and copy the code accordingly. Obviously, this is how the file will look like at the end of our hands-on-lab. You have to start with just one module (`square_content`) and add the modules we are going to create step by step. 14 | 15 | ## Verify 16 | 17 | * Use `cargo build` to verify that your project compiles. 18 | * Use `cargo clippy` to verfiy that you follow Rust coding guidelines. 19 | * Use `cargo test` to verify that all tests pass. 20 | -------------------------------------------------------------------------------- /hands-on-labs/030-simple-board-content/readme.md: -------------------------------------------------------------------------------- 1 | # Simple Board Content 2 | 3 | ## Introduction 4 | 5 | We want to implement a data structure that can hold the content of a 10x10 Battleship board. This will be a two-step process. In this exercise, we create a simple version for the board. Later on we will use a more advanced board implementation using generics. 6 | 7 | ## *simple_board_content.rs* 8 | 9 | Add the file *simple_board_content.rs* to the *battleship_game_logic* package. Copy the content from [simple_board_content.rs](../999-final/battleship_game_logic/src/simple_board_content.rs) or implement it yourself based on the included unit tests. Make yourself familiar with the code and/or discuss it with your Rust trainers. Note the recommended reading links at the beginning of the file. 10 | 11 | Don't forget to add the new module to *lib.rs*. 12 | 13 | Note: The size of the board is based on constants defined in [*lib.rs*](../999-final/battleship_game_logic/src/lib.rs). This has been done for demo purposes only. The algorithms in this package are not capable of handling a board side length other than 10. Such a feature would make algorithms unnecessarily compilicated, so we did not do it. After all, our goal is to learn and practice Rust, not to build the world's greatest Battleship game. 14 | 15 | ## Verify 16 | 17 | * Use `cargo build` to verify that your project compiles. 18 | * Use `cargo clippy` to verfiy that you follow Rust coding guidelines. 19 | * Use `cargo test` to verify that all tests pass. 20 | 21 | ## Discussions 22 | 23 | This hands-on-lab uses a [fixed-size array](https://doc.rust-lang.org/std/primitive.array.html) to store the board's content. Discuss in groups what other Rust data structure you could use to implement the Battleship board. 24 | -------------------------------------------------------------------------------- /hands-on-labs/040-generic-board-content/readme.md: -------------------------------------------------------------------------------- 1 | # Generic Board Content 2 | 3 | ## Introduction 4 | 5 | We want to implement an advanced version of our Battleship board data structure where the square content isn't fixed by specified using a type parameter. 6 | 7 | In addition to making our board generic, we also work with [iterators](https://doc.rust-lang.org/book/ch13-02-iterators.html) a lot in this exercise. Pay particular attention to the corresponding code parts. 8 | 9 | ## *generic_board_content.rs* and *row.rs* 10 | 11 | Add the files *generic_board_content.rs* and *row.rs* to the *battleship_game_logic* package. Copy the content from [generic_board_content.rs](../999-final/battleship_game_logic/src/generic_board_content.rs) and [row.rs](../999-final/battleship_game_logic/src/row.rs) or implement it yourself based on the included unit tests. Make yourself familiar with the code and/or discuss it with your Rust trainers. Note the recommended reading links at the beginning of the files. 12 | 13 | Don't forget to add the new modules to *lib.rs*. 14 | 15 | ## Verify 16 | 17 | * Use `cargo build` to verify that your project compiles. 18 | * Use `cargo clippy` to verfiy that you follow Rust coding guidelines. 19 | * Use `cargo test` to verify that all tests pass. 20 | 21 | ## Debug 22 | 23 | Add the file *.vscode/launch.json* in the root folder of your workspace. Copy the content from [.vscode/launch.json](../999-final/.vscode/launch.json). This will make it possible to debug unit tests with [*CodeLLDB*](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb). Pick a unit test, set a breakpoint, and try to debug the test code. 24 | 25 | Practice debugging throughout the following exercises. You can take the content of [.vscode/launch.json](../999-final/.vscode/launch.json) as a basis for configuring the debugger for other packages. 26 | 27 | ## Discussions 28 | 29 | Analyze how the iterator implementation on `Row` is used in the implementation of the `Display` trait for `GenericBoardContent`. 30 | -------------------------------------------------------------------------------- /hands-on-labs/050-cli/readme.md: -------------------------------------------------------------------------------- 1 | # Command Line Interface 2 | 3 | ## Introduction 4 | 5 | We have spent some time creating a library with game logic. Now we want to create a first UI. To keep it simple, we start with a command-line interface (CLI). 6 | 7 | ## CLI Binary 8 | 9 | Create a new binary target using Cargo: `cargo new --bin console_game`. Add the new package to the previously created workspace. 10 | 11 | Use `cargo build` to verify that your project compiles. 12 | 13 | ## Dependencies 14 | 15 | Copy the dependencies into the library's *Cargo.toml* from [Cargo.toml](../999-final/console_game/Cargo.toml). Lookup all the referenced crates on [crates.io](https://crates.io/) and find out what they are used for. You do not need to understand all the details. Overview knowledge is sufficient. Note how we reference our game logic in *battleship_game_logic*. 16 | 17 | ## *main.rs* 18 | 19 | Change the file *main.rs* in the *console_game* package by copying the content from [main.rs](../999-final/console_game/src/main.rs). Make yourself familiar with the code and/or discuss it with your Rust trainers. Note the recommended reading links at the beginning of the files. 20 | 21 | Note that [main.rs](../999-final/console_game/src/main.rs) contains code to randomly fill the Battleship board. We have not implemented this logic yet. Therefore, comment out the corresponding line of code. You can add it later once we covered filling the board. 22 | 23 | ## Verify 24 | 25 | * Use `cargo build` to verify that your project compiles. 26 | * Use `cargo clippy` to verfiy that you follow Rust coding guidelines. 27 | * Use `cargo run --bin console_game` to run the CLI. Try different command line parameters. 28 | 29 | ## Discussions 30 | 31 | What is the difference between the `Debug` and the `Display` trait? `SquareContent` does not implement the `Display` trait. Try to implement it and try it in the CLI. 32 | 33 | What other CLI subcommands could you think of? Try to implement an additional subcommand or command flag. 34 | -------------------------------------------------------------------------------- /hands-on-labs/060-board-index/readme.md: -------------------------------------------------------------------------------- 1 | # Board Index 2 | 3 | ## Introduction 4 | 5 | Our board content is stored in a one-dimensional array of 100 elements. However, in a Battleship game, you shoot by specifying column and row. In this exercise, we want to implement a data type that implements the indexing logic. 6 | 7 | In a Battleship game, you frequently need to specify a *range* of squares (e.g. a ship reaching from square A3 to square A6). In this exercise, we will implement a data type for that use case, too. 8 | 9 | Of course, you will again learn about a lot of important traits during this exercise. Additionally, we will look at more advanced testing strategies (in particular data-driven tests with the *rstest* crate). 10 | 11 | ## *board_index.rs* 12 | 13 | Add the files *board_index.rs* and *board_index_range.rs* to the *battleship_game_logic* package. Copy the contents from [board_index.rs](../999-final/battleship_game_logic/src/board_index.rs) and [board_index_range.rs](../999-final/battleship_game_logic/src/board_index_range.rs) or implement it yourself based on the included unit tests. Make yourself familiar with the code and/or discuss it with your Rust trainers. Note the recommended reading links at the beginning of the files. 14 | 15 | Don't forget to add the new module to *lib.rs*. 16 | 17 | ## Verify 18 | 19 | * Use `cargo build` to verify that your project compiles. 20 | * Use `cargo clippy` to verfiy that you follow Rust coding guidelines. 21 | * Use `cargo test` to verify that all tests pass. 22 | 23 | ## Discussions 24 | 25 | Extend the CLI package created before. Add a subcommand that uses logic in `BoardIndex` and/or `BoardIndexRangeInclusive` and prints the result on the screen. 26 | -------------------------------------------------------------------------------- /hands-on-labs/070-fillable-board/readme.md: -------------------------------------------------------------------------------- 1 | # Fillable Board 2 | 3 | ## Introduction 4 | 5 | So far, we have implemented useful data structures to hold the content of the board. Next, we want to focus on the game logic for placing ships. The usual Battleship rules apply: Each ship occupies a number of *consecutive* squares on the grid, arranged *either horizontally or vertically* (not diagnoally). There *must not be directly adjacent* to each other (i.e. at least one water cell has to be between ships). 6 | 7 | The following table contains the ships that are placed on each player's grid: 8 | 9 | | Class of ship | Size | 10 | | ------------- | ---: | 11 | | Carrier | 5 | 12 | | Battleship | 4 | 13 | | Cruiser | 3 | 14 | | Submarine | 3 | 15 | | Destroyer | 2 | 16 | 17 | ## *fillable_board.rs* 18 | 19 | Add the files *fillable_board.rs* and *board_filler.rs* to the *battleship_game_logic* package. Copy the content from [fillable_board.rs](../999-final/battleship_game_logic/src/fillable_board.rs) and [board_filler.rs](../999-final/battleship_game_logic/src/board_filler.rs) or implement it yourself based on the included unit tests. Make yourself familiar with the code and/or discuss it with your Rust trainers. Note the recommended reading links at the beginning of the files. 20 | 21 | Don't forget to add the new modules to *lib.rs*. 22 | 23 | Pay particular attention to the unit tests in this exercise. For the first time, we use *mock objects* using the popular *mockall* crate. Make yourself familiar with this crate. 24 | 25 | ## Verify 26 | 27 | * Use `cargo build` to verify that your project compiles. 28 | * Use `cargo clippy` to verfiy that you follow Rust coding guidelines. 29 | * Use `cargo test` to verify that all tests pass. 30 | 31 | ## Discussion 32 | 33 | Can you remember that we commented out a line of code in our CLI project? It was related to filling the board. Now that we have that logic, you can add the line of code and test whether filling the board randomly works as expected. 34 | -------------------------------------------------------------------------------- /hands-on-labs/080-ship-finder/readme.md: -------------------------------------------------------------------------------- 1 | # Ship Finder 2 | 3 | ## Introduction 4 | 5 | The next step for our game logic is code that can find out whether there is a ship on a given location. Additionally, it can recognize if the ship's location is already fully known (i.e. now unknown cells adjecent to the ship's beginning or end). This functionality is super important when e.g. building a computer player for Battleship. 6 | 7 | ## *ship_finder.rs* 8 | 9 | Add the file *ship_finder.rs* to the *battleship_game_logic* package. Copy the content from [ship_finder.rs](../999-final/battleship_game_logic/src/ship_finder.rs) or implement it yourself based on the included unit tests. Make yourself familiar with the code and/or discuss it with your Rust trainers. Note the recommended reading links at the beginning of the file. 10 | 11 | Don't forget to add the new modules to *lib.rs*. 12 | 13 | ## Verify 14 | 15 | * Use `cargo build` to verify that your project compiles. 16 | * Use `cargo clippy` to verfiy that you follow Rust coding guidelines. 17 | * Use `cargo test` to verify that all tests pass. 18 | -------------------------------------------------------------------------------- /hands-on-labs/090-single-player-game/readme.md: -------------------------------------------------------------------------------- 1 | # Single-Player Game 2 | 3 | ## Introduction 4 | 5 | In real world, Battleship games are played by two humans. In our example, a single human player plays against the computer. The computer places the ships randomly and the user's goal is to sink all ships with as few shots as possible. In this exercise we are going to implement the game logic for such single-player games. 6 | 7 | ## *single_player_game.rs* 8 | 9 | Add the file *single_player_game.rs* to the *battleship_game_logic* package. Copy the content from [single_player_game.rs](../999-final/battleship_game_logic/src/single_player_game.rs) or implement it yourself based on the included unit tests. Make yourself familiar with the code and/or discuss it with your Rust trainers. Note the recommended reading links at the beginning of the file. 10 | 11 | Don't forget to add the new modules to *lib.rs*. 12 | 13 | ## Verify 14 | 15 | * Use `cargo build` to verify that your project compiles. 16 | * Use `cargo clippy` to verfiy that you follow Rust coding guidelines. 17 | * Use `cargo test` to verify that all tests pass. 18 | 19 | ## Discussion 20 | 21 | How would you let a user play a single-player game in our existing Battleship CLI? Think about the solution and try to implement it on your own if you like. 22 | -------------------------------------------------------------------------------- /hands-on-labs/1000-console-ttt/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /hands-on-labs/1000-console-ttt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "console-ttt" 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 | colored = "2" 10 | -------------------------------------------------------------------------------- /hands-on-labs/1010-math-pyramid/math-pyramid/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /hands-on-labs/1010-math-pyramid/math-pyramid/.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 executable 'math-pyramid'", 11 | "cargo": { 12 | "args": [ 13 | "build", 14 | "--bin=math-pyramid", 15 | "--package=math-pyramid" 16 | ], 17 | "filter": { 18 | "name": "math-pyramid", 19 | "kind": "bin" 20 | } 21 | }, 22 | "args": [ 23 | "2" 24 | ], 25 | "cwd": "${workspaceFolder}" 26 | }, 27 | { 28 | "type": "lldb", 29 | "request": "launch", 30 | "name": "Debug unit tests in executable 'math-pyramid'", 31 | "cargo": { 32 | "args": [ 33 | "test", 34 | "--no-run", 35 | "--bin=math-pyramid", 36 | "--package=math-pyramid" 37 | ], 38 | "filter": { 39 | "name": "math-pyramid", 40 | "kind": "bin" 41 | } 42 | }, 43 | "args": [], 44 | "cwd": "${workspaceFolder}" 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /hands-on-labs/1010-math-pyramid/math-pyramid/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "math-pyramid" 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 | rand = "0.8" 10 | rstest = "0.18.1" 11 | -------------------------------------------------------------------------------- /hands-on-labs/1010-math-pyramid/math-pyramid/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env::args; 2 | 3 | mod args; 4 | mod ascii_art; 5 | mod pyramid; 6 | 7 | use crate::{ 8 | args::get_base_length, 9 | ascii_art::{ 10 | repeated_decorator, repeated_decorator_iter, repeated_numbers_iter, Decorator, INDENTATION, 11 | }, 12 | pyramid::calculate_pyramid, 13 | }; 14 | 15 | // ================================================================================ 16 | // LEARNINGS: 17 | // - Returning errors from main 18 | // - Match expressions 19 | // - Working with iterators 20 | // - Slices and vectors 21 | // - Type inference 22 | // - Closures 23 | // ================================================================================ 24 | 25 | fn main() -> Result<(), i32> { 26 | // Get the base length of the math pyramid from the command line arguments. 27 | let base_length = match get_base_length(args()) { 28 | Ok(n) => n, 29 | Err(e) => { 30 | eprintln!("Usage error: {}", e); 31 | return Err(1); // Indicate error by returning a non-zero exit code. 32 | } 33 | }; 34 | 35 | // Generate random numbers used as the base of the math pyramid. 36 | let random_base_numbers: Vec = (0..base_length) 37 | .map(|_| rand::random::() % 10) 38 | .collect(); 39 | 40 | // Allocate a single string containing the max indentation of the math pyramid. 41 | let max_indentation = INDENTATION.repeat(base_length - 1); 42 | 43 | // Trigger the recursive generation of the math pyramid. 44 | calculate_pyramid(&random_base_numbers, 0, &|level, numbers| { 45 | // Calculate the indentation of the current level. Note that we are not allocating 46 | // memory here. We are just using a slice of the max indentation string. 47 | let indentation = &max_indentation[0..level * INDENTATION.len()]; 48 | 49 | // Draw top border using `repeated_decorator` 50 | print!("{}", indentation); 51 | println!("{}", repeated_decorator(Decorator::Top, numbers.len())); 52 | 53 | // Draw the numbers using `repeated_numbers_iter` 54 | print!("{}", indentation); 55 | println!("{}", repeated_numbers_iter(numbers.iter().cloned())); 56 | 57 | // Draw bottom border using `repeated_decorator_iter` 58 | print!("{}", indentation); 59 | repeated_decorator_iter(Decorator::Bottom, numbers.len()).for_each(|s| print!("{s}")); 60 | println!(); 61 | }); 62 | 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /hands-on-labs/1010-math-pyramid/math-pyramid/src/pyramid.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Add; 2 | 3 | /// Recursively calculates the math pyramid. 4 | /// 5 | /// # Arguments 6 | /// 7 | /// * `numbers` - The numbers of the current level. 8 | /// * `level` - The current level. 9 | /// * `processor` - The function that is called for each level. 10 | pub fn calculate_pyramid, F: Fn(usize, &[T])>( 11 | numbers: &[T], 12 | level: usize, 13 | processor: &F, 14 | ) { 15 | // Allocate memory for the next level. 16 | let mut new_numbers = Vec::with_capacity(numbers.len() - 1); 17 | 18 | // Calculate the next level. 19 | for (a, b) in numbers.iter().zip(numbers.iter().skip(1)) { 20 | new_numbers.push(*a + *b); 21 | } 22 | 23 | // If we haven't reached the top of the pyramid, recursively calculate the next level. 24 | if numbers.len() > 1 { 25 | calculate_pyramid(&new_numbers, level + 1, processor); 26 | } 27 | 28 | // Process the current level (i.e. print it) 29 | processor(level, numbers); 30 | } 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use super::*; 35 | 36 | #[test] 37 | fn test_calculate_pyramid() { 38 | let numbers = vec![1, 2, 3]; 39 | let expected = vec![ 40 | vec![1, 2, 3], 41 | vec![3, 5], 42 | vec![8], 43 | ]; 44 | 45 | calculate_pyramid(&numbers, 0, &|level, numbers| { 46 | assert_eq!(numbers, &expected[level]); 47 | }); 48 | } 49 | } -------------------------------------------------------------------------------- /hands-on-labs/1020-tic-tac-toe/tic-tac-toe/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /hands-on-labs/1020-tic-tac-toe/tic-tac-toe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tic-tac-toe" 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 | rstest = "0.18" 10 | mockall = "0.12" 11 | -------------------------------------------------------------------------------- /hands-on-labs/1020-tic-tac-toe/tic-tac-toe/src/main.rs: -------------------------------------------------------------------------------- 1 | use board_content::BoardContent; 2 | use board_index::BoardIndex; 3 | 4 | use crate::game::WinnerDetector; 5 | 6 | mod board_content; 7 | mod square_content; 8 | mod board_index; 9 | mod game; 10 | mod row; 11 | 12 | fn main() { 13 | let mut game = game::Game::new(BoardContent::new()); 14 | 15 | println!("{game}"); 16 | 17 | while game.get_winner().is_none() { 18 | let loc = read_location(); 19 | game.set(loc).unwrap(); 20 | println!("{game}"); 21 | } 22 | 23 | println!("Winner: {:?}", game.get_winner().unwrap()); 24 | } 25 | 26 | 27 | /// Reads location from the user through stdin/stdout 28 | fn read_location() -> BoardIndex { 29 | use std::io::{stdin, stdout, Write}; 30 | 31 | loop { 32 | // Ask the user for a location 33 | print!("Enter location (A1-C3): "); 34 | stdout().flush().unwrap(); // Need to flush to ensure text is really written on the screen 35 | 36 | // Read the input 37 | let mut input = String::new(); 38 | stdin().read_line(&mut input).unwrap(); 39 | let input = input.as_bytes(); 40 | 41 | // Pare the input. Input must be in the form of A1, B2, etc. plus trailing \n 42 | if input.len() == 3 { 43 | let row = input[0] - b'A'; 44 | let col = input[1] - b'1'; 45 | if row < 3 && col < 3 { 46 | // Input is fine, end loop with the result 47 | break BoardIndex::from_col_row(col as usize, row as usize); 48 | } 49 | } 50 | 51 | // Input is invalid, ask again 52 | println!("Invalid location"); 53 | } 54 | } -------------------------------------------------------------------------------- /hands-on-labs/1020-tic-tac-toe/tic-tac-toe/src/square_content.rs: -------------------------------------------------------------------------------- 1 | // Learning: Many traits support auto-implementation using the derive macro 2 | // Todo: Copy this file into Rust Playground and choose Tools/Expand macros 3 | // to analyze what's going on behind the scenes 4 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 5 | pub enum SquareContent { 6 | X, 7 | O, 8 | } 9 | 10 | // Learning: There are system traits for type conversion. 11 | // Note: Take a look at unit tests to see how to do conversion. 12 | impl From for SquareContent { 13 | fn from(value: u8) -> Self { 14 | match value { 15 | 1 => SquareContent::X, 16 | 2 => SquareContent::O, 17 | v => panic!("Cannot convert {v} to square content"), 18 | } 19 | } 20 | } 21 | 22 | impl From for u8 { 23 | fn from(c: SquareContent) -> Self { 24 | match c { 25 | SquareContent::X => 1, 26 | SquareContent::O => 2, 27 | } 28 | } 29 | } 30 | 31 | impl From for char { 32 | fn from(value: SquareContent) -> Self { 33 | match value { 34 | SquareContent::X => 'X', 35 | SquareContent::O => 'O', 36 | } 37 | } 38 | } 39 | 40 | impl From for SquareContent { 41 | fn from(value: char) -> Self { 42 | match value { 43 | 'X' => SquareContent::X, 44 | 'O' => SquareContent::O, 45 | v => panic!("Invalid character {v}") 46 | } 47 | } 48 | } 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use super::*; 53 | 54 | #[test] 55 | fn from_into_u8() { 56 | // Learning: Implement From, get Into for free. 57 | assert_eq!(SquareContent::X, SquareContent::from(1)); 58 | assert_eq!(SquareContent::X, 1.into()); 59 | assert_eq!(1, u8::from(SquareContent::X)); 60 | assert_eq!(1u8, SquareContent::X.into()); 61 | } 62 | 63 | #[test] 64 | fn from_into_char() { 65 | assert_eq!(SquareContent::X, SquareContent::from('X')); 66 | assert_eq!(SquareContent::X, 'X'.into()); 67 | assert_eq!('X', char::from(SquareContent::X)); 68 | assert_eq!('X', Into::::into(SquareContent::X)); 69 | } 70 | 71 | #[test] 72 | #[should_panic(expected = "99")] 73 | #[allow(unused_must_use)] 74 | fn from_fails() { 75 | SquareContent::from(99); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /hands-on-labs/1021-simple-ttt/readme.md: -------------------------------------------------------------------------------- 1 | # Exercise: Simple TicTacToe 2 | 3 | ## Requirements 4 | 5 | Implement a simple, console-based TicTacToe game. 6 | 7 | The console app displays the board and asks the user where she wants to set (format *A1*, *B2*, etc.). After each set, the board is displayed again and the app asks for another location. This process has to continue until we have a winner. 8 | 9 | The following sample output illustrates the concept: 10 | 11 | ```txt 12 | ┏━━┯━━┯━━┓ 13 | ┃ | | ┃ 14 | ┠──┼──┼──┨ 15 | ┃ | | ┃ 16 | ┠──┼──┼──┨ 17 | ┃ | | ┃ 18 | ┗━━┷━━┷━━┛ 19 | 20 | Enter location (A1-C3): A1 21 | ┏━━┯━━┯━━┓ 22 | ┃XX| | ┃ 23 | ┠──┼──┼──┨ 24 | ┃ | | ┃ 25 | ┠──┼──┼──┨ 26 | ┃ | | ┃ 27 | ┗━━┷━━┷━━┛ 28 | 29 | Enter location (A1-C3): B2 30 | ┏━━┯━━┯━━┓ 31 | ┃XX| | ┃ 32 | ┠──┼──┼──┨ 33 | ┃ |OO| ┃ 34 | ┠──┼──┼──┨ 35 | ┃ | | ┃ 36 | ┗━━┷━━┷━━┛ 37 | 38 | Enter location (A1-C3): A2 39 | ┏━━┯━━┯━━┓ 40 | ┃XX|XX| ┃ 41 | ┠──┼──┼──┨ 42 | ┃ |OO| ┃ 43 | ┠──┼──┼──┨ 44 | ┃ | | ┃ 45 | ┗━━┷━━┷━━┛ 46 | 47 | Enter location (A1-C3): A3 48 | ┏━━┯━━┯━━┓ 49 | ┃XX|XX|OO┃ 50 | ┠──┼──┼──┨ 51 | ┃ |OO| ┃ 52 | ┠──┼──┼──┨ 53 | ┃ | | ┃ 54 | ┗━━┷━━┷━━┛ 55 | 56 | Enter location (A1-C3): C2 57 | ┏━━┯━━┯━━┓ 58 | ┃XX|XX|OO┃ 59 | ┠──┼──┼──┨ 60 | ┃ |OO| ┃ 61 | ┠──┼──┼──┨ 62 | ┃ |XX| ┃ 63 | ┗━━┷━━┷━━┛ 64 | 65 | Enter location (A1-C3): C1 66 | ┏━━┯━━┯━━┓ 67 | ┃XX|XX|OO┃ 68 | ┠──┼──┼──┨ 69 | ┃ |OO| ┃ 70 | ┠──┼──┼──┨ 71 | ┃OO|XX| ┃ 72 | ┗━━┷━━┷━━┛ 73 | 74 | Winner: O 75 | ``` 76 | 77 | ## Tips 78 | 79 | Here are the characters to draw the board: 80 | 81 | ```rs 82 | let top = ['┏', '━', '┯', '┓']; 83 | let middle = ['┠', '─', '┼', '┨']; 84 | let bottom = ['┗', '━', '┷', '┛']; 85 | ``` 86 | 87 | You can read from *stdin* as follows: 88 | 89 | ```rs 90 | use std::io::stdin; 91 | let mut input = String::new(); 92 | stdin().read_line(&mut input).unwrap(); 93 | ``` 94 | 95 | ## Advanced Versions 96 | 97 | * Try to separate UI logic (input from *stdin*, output to *stdout*) nicely from calculation logic. 98 | * Use system traits where appropriate (e.g. `Display` trait). 99 | -------------------------------------------------------------------------------- /hands-on-labs/1021-simple-ttt/simplettt/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /hands-on-labs/1021-simple-ttt/simplettt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simplettt" 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 | -------------------------------------------------------------------------------- /hands-on-labs/1021-simple-ttt/simplettt/src/board_index.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | #[derive(Copy, Clone)] 4 | pub struct BoardIndex(usize); 5 | 6 | impl BoardIndex { 7 | pub fn new() -> BoardIndex { 8 | BoardIndex(0) 9 | } 10 | 11 | pub fn from_col_row(col: usize, row: usize) -> BoardIndex { 12 | if col >= 3 { 13 | panic!("Column out of bounds"); 14 | } 15 | 16 | if row >= 3 { 17 | panic!("Row out of bounds"); 18 | } 19 | 20 | BoardIndex(row * 3 + col) 21 | } 22 | 23 | pub fn column(&self) -> usize { 24 | self.0 % 3 25 | } 26 | 27 | pub fn row(&self) -> usize { 28 | self.0 / 3 29 | } 30 | } 31 | 32 | impl Default for BoardIndex { 33 | fn default() -> Self { 34 | Self::new() 35 | } 36 | } 37 | 38 | // Note: Some type casting traits, similar to what we did in square_content 39 | impl From for usize { 40 | fn from(ix: BoardIndex) -> Self { 41 | ix.0 42 | } 43 | } 44 | 45 | // Learning: FromStr trait for supporting the parse method 46 | impl FromStr for BoardIndex { 47 | type Err = &'static str; 48 | 49 | fn from_str(location: &str) -> Result { 50 | let location = location.as_bytes(); // Note shadowing 51 | 52 | // Check if length of location is ok (A1..C3). 53 | if location.len() != 2 { 54 | return Err("Invalid length"); 55 | } 56 | 57 | // Parse column letter (A..C, a..c) 58 | let col = match location[0] { 59 | r @ b'A'..=b'C' => (r - b'A') as usize, 60 | r @ b'a'..=b'c' => (r - b'a') as usize, 61 | _ => return Err("Invalid column"), 62 | }; 63 | 64 | // Parse the row letter(s) (1..3) 65 | let row = match location[1] { 66 | c @ b'1'..=b'3' => (c - b'1') as usize, 67 | _ => return Err("Invalid row"), 68 | }; 69 | 70 | Ok(BoardIndex::from_col_row(col, row)) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /hands-on-labs/1021-simple-ttt/simplettt/src/main.rs: -------------------------------------------------------------------------------- 1 | use board_index::BoardIndex; 2 | 3 | use crate::{board_content::{BoardContent, SquareContent}, game::WinnerDetector}; 4 | 5 | mod board_index; 6 | mod board_content; 7 | mod game; 8 | 9 | fn main() { 10 | let mut bc = BoardContent::new(); 11 | let my_cell = bc[BoardIndex::from_col_row(0, 0)]; 12 | bc[BoardIndex::from_col_row(0, 0)] = Some(SquareContent::X); 13 | 14 | let bi: BoardIndex = "A1".parse().unwrap(); 15 | 16 | let mut game = game::Game::new(BoardContent::new()); 17 | 18 | println!("{}", game.content); 19 | 20 | while game.get_winner().is_none() { 21 | let loc = read_location(); 22 | game.set(loc).unwrap(); 23 | println!("{}", game.content); 24 | } 25 | 26 | println!("Winner: {:?}", match game.get_winner() { 27 | Some(SquareContent::X) => "X", 28 | Some(SquareContent::O) => "O", 29 | None => "None", 30 | }); 31 | } 32 | 33 | 34 | /// Reads location from the user through stdin/stdout 35 | fn read_location() -> BoardIndex { 36 | use std::io::{stdin, stdout, Write}; 37 | 38 | loop { 39 | // Ask the user for a location 40 | print!("Enter location (A1-C3): "); 41 | stdout().flush().unwrap(); // Need to flush to ensure text is really written on the screen 42 | 43 | // Read the input 44 | let mut input = String::new(); 45 | stdin().read_line(&mut input).unwrap(); 46 | let input = input.as_bytes(); 47 | 48 | // Pare the input. Input must be in the form of A1, B2, etc. plus trailing \n 49 | if input.len() == 3 { 50 | let row = input[0] - b'A'; 51 | let col = input[1] - b'1'; 52 | if row < 3 && col < 3 { 53 | // Input is fine, end loop with the result 54 | break BoardIndex::from_col_row(col as usize, row as usize); 55 | } 56 | } 57 | 58 | // Input is invalid, ask again 59 | println!("Invalid location"); 60 | } 61 | } -------------------------------------------------------------------------------- /hands-on-labs/1030-7-segments/solution/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /hands-on-labs/1030-7-segments/solution/.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}/target/debug/console_display", 12 | "args": [], 13 | "cwd": "${workspaceFolder}" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /hands-on-labs/1030-7-segments/solution/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["console_display", "seven_segments"] 4 | -------------------------------------------------------------------------------- /hands-on-labs/1030-7-segments/solution/console_display/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "console_display" 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 | crossterm = "0" 10 | seven_segments = { path = "../seven_segments" } 11 | -------------------------------------------------------------------------------- /hands-on-labs/1030-7-segments/solution/console_display/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use crossterm::{execute, terminal::ClearType}; 4 | use seven_segments::{decimal_to_segments, draw_7_segments}; 5 | 6 | fn main() { 7 | let mut stdout = std::io::stdout(); 8 | execute!(stdout, crossterm::terminal::Clear(ClearType::All)).unwrap(); 9 | execute!(stdout, crossterm::cursor::Hide{}).unwrap(); 10 | 11 | let number_to_display = 1234567890; 12 | let length_of_number; 13 | if number_to_display == 0 { 14 | length_of_number = 1; 15 | } else { 16 | length_of_number = (number_to_display as f32).log10().floor() as u32 + 1; 17 | } 18 | 19 | for ix in 0..length_of_number { 20 | let digit = (number_to_display / 10u32.pow((length_of_number - 1) - ix)) % 10; 21 | let segments = draw_7_segments(decimal_to_segments(digit as u8)); 22 | 23 | for (pos, c) in segments { 24 | let x = (pos as u16 & 0xf0) >> 4; 25 | let y = pos as u16 & 0xf; 26 | execute!(stdout, crossterm::cursor::MoveTo(x + 4 * ix as u16, y)).unwrap(); 27 | write!(stdout, "{}", c).unwrap(); 28 | } 29 | } 30 | 31 | execute!(stdout, crossterm::cursor::MoveTo(0, 4)).unwrap(); 32 | execute!(stdout, crossterm::cursor::Show{}).unwrap(); 33 | } 34 | -------------------------------------------------------------------------------- /hands-on-labs/1030-7-segments/solution/seven_segments/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "seven_segments" 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 | 10 | [dev-dependencies] 11 | rstest = "0" 12 | -------------------------------------------------------------------------------- /hands-on-labs/200-web-api-getting-started/readme.md: -------------------------------------------------------------------------------- 1 | # Battleship Web API 2 | 3 | ## Introduction 4 | 5 | In the following exercises, we want to build a web API for playing Battleship games including a simplified web client (just JavaScript, no fancy SPA framework). 6 | 7 | You can choose from a [huge amount of frameworks for web programming in Rust](https://github.com/rust-unofficial/awesome-rust#web-programming). In this exercise, we are going to use the [Rocket](https://rocket.rs/) framework. 8 | 9 | ## Create Package 10 | 11 | Create a new binary target using Cargo: `cargo new --bin battleship_web_api`. Add the new package to the previously created workspace. 12 | 13 | Use `cargo build` to verify that your project compiles. 14 | 15 | ## Dependencies 16 | 17 | Copy the dependencies into the library's *Cargo.toml* from [Cargo.toml](../999-final/battleship_web_api/Cargo.toml). Lookup all the referenced crates on [crates.io](https://crates.io/) and find out what they are used for. You do not need to understand all the details. Overview knowledge is sufficient. Note how we reference our game logic in *battleship_game_logic*. 18 | 19 | **Note:** At the time of writing, Rocket 0.5 has not been released yet. Therefore, this exercise is built on the current release candidate. Use the released version of Rocket 0.5 if it is already available when you do this exercise. 20 | 21 | ## Hello World 22 | 23 | Copy the *Hello World* example from [Rocket's documentation](https://rocket.rs/v0.5-rc/guide/getting-started/#hello-world) into your *main.rs* file. Start the web server with `cargo run` and verify that you get a proper response. 24 | 25 | For testing the web API, we recommend one of the following VSCode extensions: 26 | 27 | * [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) 28 | * [Thunder Client](https://marketplace.visualstudio.com/items?itemName=rangav.vscode-thunder-client) 29 | 30 | Note that this repository contains sample requests for the *REST Client* in [requests.http](../999-final/battleship_web_api/requests.http). 31 | -------------------------------------------------------------------------------- /hands-on-labs/210-game-repository/readme.md: -------------------------------------------------------------------------------- 1 | # Game Repository 2 | 3 | ## Introduction 4 | 5 | Rocket has powerful features built-in for managing state in memory or in databases ([see docs](https://rocket.rs/v0.5-rc/guide/state/)). We want to keep things simple here because we are just learning Rust fundamentals. Therefore, we are going to implement a simple in-memory repository of Battleship games. 6 | 7 | ## *game_repository.rs* 8 | 9 | Add the file *game_repository.rs* to the *battleship_web_api* package. Copy the content from [game_repository.rs](../999-final/battleship_web_api/src/game_repository.rs) or implement it yourself based on the included unit tests. Make yourself familiar with the code and/or discuss it with your Rust trainers. Note the recommended reading links at the beginning of the file. 10 | -------------------------------------------------------------------------------- /hands-on-labs/220-web-api/readme.md: -------------------------------------------------------------------------------- 1 | # Web API 2 | 3 | ## Introduction 4 | 5 | Now it is time to add the web API. If you have experience with web API development in other languages, building basic APIs with Rocket will probably not be difficult for you. To make things more interesting, we have added API testing with mock objects (a mock version of our game repository). 6 | 7 | ## *main.rs* 8 | 9 | Change the file *main.rs* in the *battleship_web_api* package. Copy the content from [main.rs](../999-final/battleship_web_api/src/main.rs) or implement it yourself based on the included unit tests. Make yourself familiar with the code and/or discuss it with your Rust trainers. Note the recommended reading links at the beginning of the file. 10 | 11 | ## Verify 12 | 13 | * Use `cargo build` to verify that your project compiles. 14 | * Use `cargo clippy` to verfiy that you follow Rust coding guidelines. 15 | * Use `cargo test` to verify that all tests pass. 16 | 17 | If you use VSCode's *REST Client*, you can use the sample requests in [requests.http](../999-final/battleship_web_api/requests.http) to test your API. 18 | -------------------------------------------------------------------------------- /hands-on-labs/230-browser-client/readme.md: -------------------------------------------------------------------------------- 1 | # Browser Client 2 | 3 | ## Introduction 4 | 5 | Now that we have a working API, we need a UI. As this training focusses on Rust, we keep the browser UI very simply. It just consists of a hand full of static HTML, JS, and CSS files. However, we want that our Rocket server serves the static files to the browser. 6 | 7 | ## Static File Serving 8 | 9 | Our *main.rs* file already contains the code for serving static files from a folder named *public*. Try to find the corresponding line of code in *main.rs*. 10 | 11 | ## Adding Client 12 | 13 | Create a new folder *public* in your *battleship_web_api* folder. Copy the following files into that folder and make yourself familiar with the code. 14 | 15 | * [index.html](../999-final/battleship_web_api/public/index.html) 16 | * [index.css](../999-final/battleship_web_api/public/index.css) 17 | * [index.js](../999-final/battleship_web_api/public/index.js) 18 | 19 | ## Verify 20 | 21 | Run your web API project with `cargo run` and use your favorite web browser to launch the UI. Inspect the network traffic to see communication from JS in the browser with Rust on the backend. 22 | -------------------------------------------------------------------------------- /hands-on-labs/300-wasm-getting-started/readme.md: -------------------------------------------------------------------------------- 1 | # WebAssembly Battleship 2 | 3 | ## Introduction 4 | 5 | Rust works very well with WebAssembly. Therefore we are going to implement a version of our Battleship browser UI in which the entire game logic runs within the browser. Of course we want to reuse our existing game logic for that purpose. 6 | 7 | ## Create Package 8 | 9 | We will follow the [*Getting Started* guide in the Rust WASM book](https://rustwasm.github.io/docs/book/game-of-life/hello-world.html) to create our new package. Run `cargo generate --git https://github.com/rustwasm/wasm-pack-template` and use *battleship_wasm* as your project name. 10 | 11 | If you want, you can delete files that are not relevant for learning Rust (e.g. readme-files, license-files, etc.). 12 | 13 | Use [*wasm-pack*](https://rustwasm.github.io/wasm-pack/book/) to build the code: `wasm-pack build` 14 | 15 | ## Dependencies 16 | 17 | Overwrite the dependencies in the library's *Cargo.toml* with the code from [Cargo.toml](../999-final/battleship_wasm/Cargo.toml). Lookup all the referenced crates on [crates.io](https://crates.io/) and find out what they are used for. You do not need to understand all the details. Overview knowledge is sufficient. Note how we reference our game logic in *battleship_game_logic*. 18 | 19 | **Note:** In this example, we do not use [*wee_alloc*](https://github.com/rustwasm/wee_alloc) for WASM size optimization. 20 | 21 | ## *lib.rs* 22 | 23 | Change the file *lib.rs* in the *battleship_wasm* package by copying the content from [lib.rs](../999-final/battleship_wasm/src/lib.rs). Make yourself familiar with the code and/or discuss it with your Rust trainers. Note the recommended reading links at the beginning of the file. 24 | 25 | ## *web.rs* 26 | 27 | Change the file *web.rs* in the *battleship_wasm tests* package by copying the content from [web.rs](../999-final/battleship_wasm/tests/web.rs). Make yourself familiar with the code and/or discuss it with your Rust trainers. Note the recommended reading links at the beginning of the file. 28 | 29 | ## Verify 30 | 31 | * Use `wasm-pack build` to verify that your project compiles. 32 | * Use `cargo clippy` to verfiy that you follow Rust coding guidelines. 33 | * Use `wasm-pack test` to verify that all tests pass. 34 | -------------------------------------------------------------------------------- /hands-on-labs/310-wasm-browser/readme.md: -------------------------------------------------------------------------------- 1 | # WebAssembly Browser 2 | 3 | ## Introduction 4 | 5 | Now that we have the WASM package, we need a web client. The [Rust WASM book](https://rustwasm.github.io/docs/book/game-of-life/hello-world.html#putting-it-into-a-web-page) contains a description how to setup a basic web client. In this exercise we do **not** follow this guide because it reference some older packages and uses JavaScript instead of TypeScript. 6 | 7 | ## Create Node Package 8 | 9 | Create a subfolder *www* in the *battleship_wasm* package. Copy all files from [www](../999-final/battleship_wasm/www). Make yourself familiar with the code and/or discuss it with your Rust trainers. 10 | 11 | ## Dependencies 12 | 13 | Take a look at the *package.json* file. Note how it references the WASM package. 14 | 15 | Install the required Node packages by running `npm install` in the *www* folder. 16 | 17 | ## Verify 18 | 19 | Run your *www* project with `npm start` and use your favorite web browser to launch the UI. Inspect the network traffic to see how the WASM module is loaded from the server. 20 | -------------------------------------------------------------------------------- /hands-on-labs/400-rusty-nerdle/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nerdle" 3 | version = "0.1.0" 4 | authors = ["Rainer "] 5 | edition = "2021" 6 | 7 | # Crate types cdylib and rlib are important. 8 | # cdylib is for creating a .wasm file. rlib is important for testing with "wasm-pack test". 9 | # Read more at https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/template-deep-dive/cargo-toml.html#1-crate-type 10 | [lib] 11 | crate-type = ["cdylib", "rlib"] 12 | 13 | [dependencies] 14 | # wasm-bindgen is the most important dependency. Read more at 15 | # https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/template-deep-dive/cargo-toml.html#2-wasm-bindgen-dependency. 16 | # "serde-serialize" is important for working with JsValue. Read more at 17 | # https://rustwasm.github.io/docs/wasm-bindgen/reference/arbitrary-data-with-serde.html 18 | wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } 19 | console_error_panic_hook = "0.1" 20 | serde = { version = "1.0", features = ["derive"] } 21 | serde-wasm-bindgen = "0.4" 22 | rand = "^0.8.0" 23 | getrandom = { version = "^0.2.0", features = [ "js" ] } 24 | 25 | [dev-dependencies] 26 | wasm-bindgen-test = "0.3" 27 | evalexpr = "7" 28 | -------------------------------------------------------------------------------- /hands-on-labs/400-rusty-nerdle/rusty-nerdle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-linz/rust-fundamentals-training/9033deff4103b96bbc0f0749a9eb4fbaed0a1825/hands-on-labs/400-rusty-nerdle/rusty-nerdle.png -------------------------------------------------------------------------------- /hands-on-labs/400-rusty-nerdle/www/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nerdle-ui", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack --config webpack.config.js", 8 | "start": "webpack serve" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "css-loader": "^6.5.0", 14 | "html-webpack-plugin": "^5.5.0", 15 | "style-loader": "^3.3.1", 16 | "ts-loader": "^9.2.6", 17 | "typescript": "^4.4.4", 18 | "webpack": "^5.60.0", 19 | "webpack-cli": "^4.9.1", 20 | "webpack-dev-server": "^4.3.1" 21 | }, 22 | "dependencies": { 23 | "nerdle": "file:../pkg" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /hands-on-labs/400-rusty-nerdle/www/src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | // A dependency graph that contains any wasm must all be imported 2 | // asynchronously. This `bootstrap.js` file does the single async import, so 3 | // that no one else needs to worry about it again. 4 | import("./index") 5 | .catch(e => console.error("Error importing `index.js`:", e)); 6 | -------------------------------------------------------------------------------- /hands-on-labs/400-rusty-nerdle/www/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const ROWS = 6; 2 | export const COLS = 7; 3 | -------------------------------------------------------------------------------- /hands-on-labs/400-rusty-nerdle/www/src/game.ts: -------------------------------------------------------------------------------- 1 | import { generateChallenge } from "nerdle"; 2 | import { Board } from "./board"; 3 | import { COLS, ROWS } from "./constants"; 4 | import { Result, CharState } from "./result"; 5 | 6 | export class Game { 7 | private equation: string; 8 | private expectedResult: number; 9 | 10 | constructor(container: HTMLDivElement) { 11 | this.createEquation(); 12 | new Board(container, this.expectedResult, (input, numberOfGuesses) => 13 | this.evaluateInput(input, numberOfGuesses) 14 | ); 15 | } 16 | 17 | private evaluateInput(input: string, numberOfGuesses: number): Result { 18 | const result = new Result(); 19 | 20 | let evalResult: number; 21 | try { 22 | evalResult = eval(input); 23 | } catch (error) { 24 | result.error = "Could not evaluate input."; 25 | } 26 | 27 | if (evalResult !== undefined) { 28 | if (evalResult !== this.expectedResult) { 29 | result.error = "Input is not valid"; 30 | } else { 31 | const availableChars: string[] = []; 32 | 33 | for (let i = 0; i < COLS; i++) { 34 | if (this.equation[i] !== input[i]) { 35 | availableChars.push(this.equation[i]); 36 | } 37 | } 38 | 39 | for (let i = 0; i < COLS; i++) { 40 | let state: CharState = "notInSolution"; 41 | if (this.equation[i] === input[i]) { 42 | state = "correctSpot"; 43 | } else if (availableChars.includes(input[i])) { 44 | const index = availableChars.indexOf(input[i]); 45 | availableChars.splice(index, 1); 46 | state = "wrongSpot"; 47 | } 48 | result.input.push({ key: input[i], state: state }); 49 | } 50 | } 51 | } 52 | 53 | if (!result.error) { 54 | if (!result.input.find((i) => i.state !== "correctSpot")) { 55 | result.status = "won"; 56 | } else if (numberOfGuesses === ROWS - 1) { 57 | result.status = "lost"; 58 | } 59 | } 60 | 61 | return result; 62 | } 63 | 64 | private createEquation(): void { 65 | // <<< HERE we call our challenge generation written in Rust 66 | const challenge: { formula: string; result: number } = generateChallenge(); 67 | this.equation = challenge.formula; 68 | this.expectedResult = challenge.result; 69 | 70 | // Print equation and result in console. Yes, I know, we are cheating... 71 | console.log(this.equation); 72 | console.log(this.expectedResult); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /hands-on-labs/400-rusty-nerdle/www/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Courier New", Courier, monospace; 3 | width: 100vw; 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: center; 7 | align-items: center; 8 | text-align: center; 9 | } 10 | 11 | #header { 12 | margin-bottom: 15px; 13 | } 14 | 15 | #output-area { 16 | display: grid; 17 | grid-template-columns: repeat(7, auto); 18 | grid-gap: 3px; 19 | } 20 | 21 | #error-area { 22 | margin-top: 30px; 23 | color: #dd0000; 24 | font-weight: bold; 25 | } 26 | 27 | .output-field { 28 | background-color: #bbb; 29 | border-radius: 5px; 30 | width: 50px; 31 | height: 50px; 32 | font-size: 20px; 33 | } 34 | 35 | #keyboard-area { 36 | margin-top: 30px; 37 | display: grid; 38 | grid-template-columns: repeat(10, 1fr); 39 | grid-gap: 3px; 40 | } 41 | 42 | .key { 43 | background-color: #ddd; 44 | border-radius: 5px; 45 | width: auto; 46 | height: 50px; 47 | font-size: 16px; 48 | cursor: pointer; 49 | } 50 | 51 | .key:hover { 52 | filter: #bbb; 53 | } 54 | 55 | .key:active { 56 | filter: #999; 57 | } 58 | 59 | .key.enter { 60 | grid-area: span 1 / span 4; 61 | } 62 | 63 | .key.delete { 64 | grid-area: span 1 / span 2; 65 | } 66 | 67 | .key, 68 | .output-field { 69 | display: flex; 70 | justify-content: center; 71 | align-items: center; 72 | font-weight: bold; 73 | } 74 | -------------------------------------------------------------------------------- /hands-on-labs/400-rusty-nerdle/www/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Nerdle 8 | 9 | 10 |

    Rusty Nerdle

    11 |
    12 | 13 | -------------------------------------------------------------------------------- /hands-on-labs/400-rusty-nerdle/www/src/index.ts: -------------------------------------------------------------------------------- 1 | import './index.css'; 2 | 3 | import { Game } from './game'; 4 | import { run } from 'nerdle'; 5 | 6 | // <<< HERE we call our initialization function in Rust 7 | run(); 8 | new Game(document.querySelector('#app')); 9 | -------------------------------------------------------------------------------- /hands-on-labs/400-rusty-nerdle/www/src/result.ts: -------------------------------------------------------------------------------- 1 | export type State = 'inProgress' | 'won' | 'lost'; 2 | export type CharState = 'correctSpot' | 'wrongSpot' | 'notInSolution'; 3 | 4 | export class Result { 5 | error = ''; 6 | status: State = 'inProgress'; 7 | input: { 8 | key: string; 9 | state: CharState; 10 | }[] = []; 11 | } 12 | -------------------------------------------------------------------------------- /hands-on-labs/400-rusty-nerdle/www/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "sourceMap": true, 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": false, 9 | "moduleResolution": "node" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /hands-on-labs/400-rusty-nerdle/www/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HTMLWebpackPlugin = require('html-webpack-plugin'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | entry: "./src/bootstrap.ts", 6 | devtool: 'inline-source-map', 7 | experiments: { 8 | syncWebAssembly: true 9 | }, 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.tsx?$/, 14 | use: 'ts-loader', 15 | exclude: /node_modules/, 16 | }, 17 | { 18 | test: /\.css$/i, 19 | use: ["style-loader", "css-loader"], 20 | }, 21 | ], 22 | }, 23 | resolve: { 24 | extensions: ['.tsx', '.ts', '.js'], 25 | }, 26 | output: { 27 | path: path.resolve(__dirname, "dist"), 28 | filename: "bundle.js", 29 | }, 30 | mode: "development", 31 | plugins: [ 32 | new HTMLWebpackPlugin({ 33 | template: path.resolve(__dirname, 'src/index.html'), 34 | }), 35 | ] 36 | }; 37 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/.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": "Cargo test", 11 | "program": "${cargo:program}", 12 | "cargo": { 13 | "args": [ 14 | "test", 15 | "--no-run", 16 | "--lib" 17 | ] 18 | }, 19 | "args": [] 20 | }, 21 | { 22 | "type": "lldb", 23 | "request": "launch", 24 | "name": "Cargo run web api", 25 | "program": "${workspaceRoot}/target/debug/battleship_web_api", 26 | "args": [], 27 | "cwd": "${workspaceRoot}", 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /hands-on-labs/999-final/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.unsetTest": ["core"] 3 | } -------------------------------------------------------------------------------- /hands-on-labs/999-final/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "battleship_game_logic", 4 | "console_game", 5 | "battleship_web_api", 6 | "battleship_wasm", 7 | ] -------------------------------------------------------------------------------- /hands-on-labs/999-final/battleship_game_logic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "battleship_game_logic" 3 | version = "0.1.0" 4 | authors = ["Rainer "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | rand = "^0.8.0" 9 | getrandom = { version = "^0.2.0", features = [ "js" ] } 10 | 11 | [dev-dependencies] 12 | rstest = "^0.15.0" 13 | mockall = "^0.11.0" 14 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/battleship_game_logic/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Note: Loads the contents of the module square_content from another file 2 | // with the same name as the module. Read more at 3 | // https://doc.rust-lang.org/book/ch07-05-separating-modules-into-different-files.html 4 | mod square_content; 5 | mod simple_board_content; 6 | mod generic_board_content; 7 | mod row; 8 | mod board_index; 9 | mod fillable_board; 10 | mod board_filler; 11 | mod single_player_game; 12 | mod board_index_range; 13 | mod ship_finder; 14 | 15 | // Note use of consts here. This has been done for demo purposes only. 16 | // The algorithms in this package are NOT capable of handling 17 | // a board side length other than 10. Such a feature would make 18 | // algorithms more compilicated -> out of scope of the workshop. 19 | const BOARD_SIDE_LENGTH: usize = 10; 20 | pub const BOARD_SIZE: usize = BOARD_SIDE_LENGTH * BOARD_SIDE_LENGTH; 21 | 22 | // Note: Re-exports the content of the square_content module to keep paths short. 23 | // Read more at https://doc.rust-lang.org/reference/items/use-declarations.html#use-visibility 24 | pub use crate::square_content::*; 25 | pub use crate::simple_board_content::*; 26 | pub use crate::generic_board_content::*; 27 | pub use crate::row::*; 28 | pub use crate::board_index::*; 29 | pub use crate::fillable_board::*; 30 | pub use crate::board_filler::*; 31 | pub use crate::single_player_game::*; 32 | pub use crate::board_index_range::*; 33 | pub use crate::ship_finder::*; 34 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/battleship_wasm/.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | www/node_modules 3 | www/.bin 4 | www/dist 5 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/battleship_wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "battleship_wasm" 3 | version = "0.1.0" 4 | authors = ["Rainer "] 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [features] 11 | default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | wasm-bindgen = { version = "^0.2.0", features = [ "serde-serialize" ] } 15 | battleship_game_logic = { path = "../battleship_game_logic" } 16 | serde = { version = "^1.0.0", features = ["derive"] } 17 | 18 | # The `console_error_panic_hook` crate provides better debugging of panics by 19 | # logging them with `console.error`. This is great for development, but requires 20 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 21 | # code size when deploying. 22 | console_error_panic_hook = { version = "^0.1.0", optional = true } 23 | 24 | [dev-dependencies] 25 | wasm-bindgen-test = "^0.3.0" 26 | 27 | [profile.release] 28 | # Tell `rustc` to optimize for small code size. 29 | opt-level = "s" 30 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/battleship_wasm/src/utils.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | pub fn set_panic_hook() { 3 | // When the `console_error_panic_hook` feature is enabled, we can call the 4 | // `set_panic_hook` function at least once during initialization, and then 5 | // we will get better error messages if our code ever panics. 6 | // 7 | // For more details see 8 | // https://github.com/rustwasm/console_error_panic_hook#readme 9 | #[cfg(feature = "console_error_panic_hook")] 10 | console_error_panic_hook::set_once(); 11 | } 12 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/battleship_wasm/tests/web.rs: -------------------------------------------------------------------------------- 1 | #![cfg(target_arch = "wasm32")] 2 | 3 | extern crate wasm_bindgen_test; 4 | use std::assert_eq; 5 | use battleship_wasm::SquareContentJS; 6 | use battleship_game_logic::SquareContent; 7 | 8 | use wasm_bindgen_test::*; 9 | 10 | wasm_bindgen_test_configure!(run_in_browser); 11 | 12 | #[wasm_bindgen_test] 13 | fn square_content() { 14 | assert_eq!(SquareContentJS::Water as u8, SquareContent::Water as u8); 15 | assert_eq!(SquareContentJS::HitShip as u8, SquareContent::HitShip as u8); 16 | assert_eq!(SquareContentJS::SunkenShip as u8, SquareContent::SunkenShip as u8); 17 | } 18 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/battleship_wasm/www/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "battleship_wasm_www", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack --config webpack.config.js", 8 | "start": "webpack serve" 9 | }, 10 | "devDependencies": { 11 | "base64-loader": "^1.0.0", 12 | "css-loader": "^6.5.0", 13 | "hello-wasm-pack": "^0.1.0", 14 | "html-webpack-plugin": "^5.5.0", 15 | "style-loader": "^3.3.1", 16 | "ts-loader": "^9.2.6", 17 | "typescript": "^4.4.4", 18 | "webpack": "^5.60.0", 19 | "webpack-cli": "^4.9.1", 20 | "webpack-dev-server": "^4.3.1" 21 | }, 22 | "dependencies": { 23 | "battleship_wasm": "file:../pkg" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/battleship_wasm/www/src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | // A dependency graph that contains any wasm must all be imported 2 | // asynchronously. This `bootstrap.js` file does the single async import, so 3 | // that no one else needs to worry about it again. 4 | import("./index") 5 | .catch(e => console.error("Error importing `index.js`:", e)); 6 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/battleship_wasm/www/src/index.css: -------------------------------------------------------------------------------- 1 | td { 2 | width: 30px; 3 | height: 30px; 4 | border: 1px solid black; 5 | } 6 | 7 | td.water { 8 | background-color: aqua; 9 | } 10 | 11 | td.hit { 12 | background-color: yellow; 13 | } 14 | 15 | td.sunken { 16 | background-color: red; 17 | } 18 | 19 | table { 20 | border-collapse: collapse; 21 | } 22 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/battleship_wasm/www/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Battleship 8 | 9 | 10 |

    Battleship

    11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/battleship_wasm/www/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as bs from "battleship_wasm"; 2 | import { memory } from "battleship_wasm/battleship_wasm_bg.wasm"; 3 | import "./index.css"; 4 | 5 | const game = bs.BattleshipGame.new(); 6 | const boardContent = new Uint8Array(memory.buffer, game.board_content(), 100); 7 | 8 | const board = document.getElementById('board') as HTMLTableElement; 9 | const won = document.getElementById('won') as HTMLHeadingElement; 10 | 11 | for(let row = 0; row < 10; row++) { 12 | const newRow = board.insertRow(-1); 13 | for(let col = 0; col < 10; col++) { 14 | const newCell = newRow.insertCell(-1); 15 | newCell.dataset.loc = `${ String.fromCharCode('A'.charCodeAt(0) + col) }${row + 1}`; 16 | newCell.addEventListener("click", elem => shoot((elem.target as HTMLTableElement).dataset.loc)); 17 | } 18 | } 19 | 20 | async function shoot(location: string) { 21 | const result = game.shoot(location); 22 | if (result.game_state === "AllShipsSunken") { 23 | won.hidden = false; 24 | } 25 | 26 | let ix = 0; 27 | for(const tr of board.lastElementChild.children) { 28 | for(const td of tr.children) { 29 | switch (boardContent[ix++]) { 30 | case bs.SquareContentJS.Water: 31 | td.className = 'water' 32 | break; 33 | case bs.SquareContentJS.HitShip: 34 | td.className = 'hit' 35 | break; 36 | case bs.SquareContentJS.SunkenShip: 37 | td.className = 'sunken' 38 | break; 39 | default: 40 | break; 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/battleship_wasm/www/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "sourceMap": true, 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": false, 9 | "moduleResolution": "node" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/battleship_wasm/www/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HTMLWebpackPlugin = require('html-webpack-plugin'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | entry: "./src/bootstrap.ts", 6 | experiments: { 7 | syncWebAssembly: true 8 | }, 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.tsx?$/, 13 | use: 'ts-loader', 14 | exclude: /node_modules/, 15 | }, 16 | { 17 | test: /\.css$/i, 18 | use: ["style-loader", "css-loader"], 19 | }, 20 | ], 21 | }, 22 | resolve: { 23 | extensions: ['.tsx', '.ts', '.js'], 24 | }, 25 | output: { 26 | path: path.resolve(__dirname, "dist"), 27 | filename: "bundle.js", 28 | }, 29 | mode: "development", 30 | plugins: [ 31 | new HTMLWebpackPlugin({ 32 | template: path.resolve(__dirname, 'src/index.html'), 33 | }), 34 | ], 35 | ignoreWarnings: [ 36 | { 37 | // Read more about webpack warning at https://github.com/rustwasm/wasm-pack/issues/822 38 | module: /\.\.\/pkg\/battleship_wasm_bg\.js/, 39 | } 40 | ] 41 | }; 42 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/battleship_web_api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "battleship_web_api" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | rocket = { version = "0.5.0-rc.2", features = [ "uuid", "json" ] } 8 | uuid = { version = "^1.0.0", features = ["serde", "v4"] } 9 | battleship_game_logic = { path = "../battleship_game_logic" } 10 | mockall_double = "^0.3.0" 11 | 12 | [dev-dependencies] 13 | serde_json = "^1.0.0" 14 | mockall = "^0.11.0" 15 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/battleship_web_api/public/index.css: -------------------------------------------------------------------------------- 1 | td { 2 | width: 30px; 3 | height: 30px; 4 | border: 1px solid black; 5 | } 6 | 7 | td.water { 8 | background-color: aqua; 9 | } 10 | 11 | td.hit { 12 | background-color: yellow; 13 | } 14 | 15 | td.sunken { 16 | background-color: red; 17 | } 18 | 19 | table { 20 | border-collapse: collapse; 21 | } 22 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/battleship_web_api/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Battleship 8 | 9 | 10 | 11 |

    Battleship

    12 | 13 |
    14 |

    Start Game

    15 |

    16 | Please enter your name: 17 | 18 |

    19 | 20 |
    21 | 22 |
    25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/battleship_web_api/requests.http: -------------------------------------------------------------------------------- 1 | @host = http://localhost:8000 2 | 3 | ### 4 | # @name add_game 5 | POST {{host}}/games 6 | Content-Type: application/json 7 | 8 | { 9 | "player": "Rainer" 10 | } 11 | 12 | ### 13 | @game_id = {{add_game.response.body.$}} 14 | 15 | GET {{host}}/games/{{game_id}} 16 | Accept: application/json 17 | 18 | ### 19 | @game_id = {{add_game.response.body.$}} 20 | 21 | POST {{host}}/games/{{game_id}}/shoot 22 | Accept: application/json 23 | Content-Type: application/json 24 | 25 | "B1" 26 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/battleship_web_api/src/game_repository.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, sync::RwLock}; 2 | 3 | use battleship_game_logic::{BoardIndex, SinglePlayerGame}; 4 | use uuid::Uuid; 5 | 6 | /* 7 | Learnings in this module: 8 | 9 | * Mocking structs for unit tests 10 | * Conditional compilation 11 | 12 | Recommended readings for this module: 13 | 14 | * Mockall - mocking structs: https://docs.rs/mockall/0.10.1/mockall/#mocking-structs 15 | * Conditional compilation: https://doc.rust-lang.org/reference/conditional-compilation.html 16 | */ 17 | 18 | pub type ID = Uuid; 19 | 20 | #[derive(Debug, Clone)] 21 | pub struct Game { 22 | pub id: Uuid, 23 | pub player: String, 24 | pub game: SinglePlayerGame, 25 | } 26 | 27 | pub struct GameRepository { 28 | #[allow(dead_code)] 29 | games: RwLock>, 30 | } 31 | 32 | #[cfg(test)] 33 | mockall::mock! { 34 | pub GameRepository { 35 | pub fn get_by_id(&self, id: &ID) -> Option; 36 | pub fn add(&self, player: String) -> Game; 37 | pub fn shoot(&self, id: &ID, location: BoardIndex) -> Option; 38 | } 39 | } 40 | 41 | impl Default for GameRepository { 42 | fn default() -> Self { 43 | GameRepository::new() 44 | } 45 | } 46 | 47 | impl GameRepository { 48 | pub fn new() -> GameRepository { 49 | GameRepository { games: RwLock::new(HashMap::new()) } 50 | } 51 | 52 | #[allow(dead_code)] 53 | pub fn add(&self, player: String) -> Game { 54 | let id = Uuid::new_v4(); 55 | let new_game = Game { id, player, game: SinglePlayerGame::new() }; 56 | 57 | let mut games = self.games.write().unwrap(); 58 | games.insert(id, new_game.clone()); 59 | new_game 60 | } 61 | 62 | #[allow(dead_code)] 63 | pub fn shoot(&self, id: &ID, location: BoardIndex) -> Option { 64 | let mut map = self.games.write().unwrap(); 65 | let game = map.get_mut(id)?; 66 | 67 | game.game.shoot(location); 68 | Some(game.clone()) 69 | } 70 | 71 | #[allow(dead_code)] 72 | pub fn get_by_id(&self, id: &ID) -> Option { 73 | let map = self.games.read().unwrap(); 74 | let game = map.get(id)?; 75 | Some(game.clone()) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/console_game/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "console_game" 3 | version = "0.1.0" 4 | authors = ["Rainer "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | battleship_game_logic = { path = "../battleship_game_logic" } 9 | structopt = "^0.3.0" 10 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/console_game/src/main.rs: -------------------------------------------------------------------------------- 1 | use battleship_game_logic::{BattleshipBoardContent, BoardFiller, SquareContent, random_placer}; 2 | use structopt::StructOpt; 3 | 4 | /* 5 | Learnings in this module: 6 | 7 | * Build a CLI with Rust 8 | 9 | Recommended readings for this module: 10 | 11 | * structopt crate: https://docs.rs/structopt/0.3.22/structopt/index.html 12 | */ 13 | 14 | #[derive(StructOpt)] 15 | #[structopt()] 16 | enum Command { 17 | SquareContent, 18 | Board { 19 | #[structopt(short, long, help = "Indicates whether the board should be filled")] 20 | fill: bool 21 | }, 22 | } 23 | 24 | fn main() { 25 | match Command::from_args() { 26 | Command::SquareContent => square_content(), 27 | Command::Board { fill } => board(fill), 28 | } 29 | } 30 | 31 | fn square_content() { 32 | let content = SquareContent::default(); 33 | println!("Debug: {:?}", content); 34 | 35 | let content: SquareContent = '~'.into(); 36 | println!("Parsed: {:?}", content); 37 | println!("Parsed Display: {}", char::from(content)); 38 | } 39 | 40 | fn board(fill: bool) { 41 | let mut board = BattleshipBoardContent::new_initialized(SquareContent::Water); 42 | if fill { board.fill(&[5, 4, 3, 3, 2], random_placer); } 43 | 44 | println!("Filled board:\n{}", board); 45 | } 46 | -------------------------------------------------------------------------------- /hands-on-labs/999-final/rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_small_heuristics = "Max" 2 | -------------------------------------------------------------------------------- /slides/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | max_line_length = off 9 | -------------------------------------------------------------------------------- /slides/.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "MD012": false, 3 | "MD013": false, 4 | "MD024": false, 5 | "MD025": false 6 | } 7 | -------------------------------------------------------------------------------- /slides/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "editorconfig.editorconfig", 8 | "yzhang.markdown-all-in-one", 9 | "DavidAnson.vscode-markdownlint", 10 | "ban.spellright" 11 | ], 12 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 13 | "unwantedRecommendations": [ 14 | 15 | ] 16 | } -------------------------------------------------------------------------------- /slides/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-linz/rust-fundamentals-training/9033deff4103b96bbc0f0749a9eb4fbaed0a1825/slides/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /slides/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-linz/rust-fundamentals-training/9033deff4103b96bbc0f0749a9eb4fbaed0a1825/slides/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /slides/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-linz/rust-fundamentals-training/9033deff4103b96bbc0f0749a9eb4fbaed0a1825/slides/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /slides/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /slides/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-linz/rust-fundamentals-training/9033deff4103b96bbc0f0749a9eb4fbaed0a1825/slides/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /slides/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-linz/rust-fundamentals-training/9033deff4103b96bbc0f0749a9eb4fbaed0a1825/slides/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /slides/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-linz/rust-fundamentals-training/9033deff4103b96bbc0f0749a9eb4fbaed0a1825/slides/favicon/favicon.ico -------------------------------------------------------------------------------- /slides/favicon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-linz/rust-fundamentals-training/9033deff4103b96bbc0f0749a9eb4fbaed0a1825/slides/favicon/mstile-150x150.png -------------------------------------------------------------------------------- /slides/favicon/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /slides/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rust-fundamentals-training-slides", 3 | "version": "1.0.0", 4 | "description": "Slides for Rust fundamentals training", 5 | "author": "Rainer Stropek, Stefan Baumgartner", 6 | "license": "MIT", 7 | "scripts": { 8 | "clean": "node ./util/clean.js", 9 | "build": "node ./util/build.js", 10 | "watch-build": "watch 'npm run build' src util --interval=1", 11 | "watch-start": "live-server --port=8080 --quiet dist", 12 | "start": "npm-run-all -p watch-*" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/rust-linz/rust-fundamentals-training.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/rust-linz/rust-fundamentals-training/issues" 20 | }, 21 | "homepage": "https://github.com/rust-linz/rust-fundamentals-training#readme", 22 | "dependencies": { 23 | "reveal.js": "^5.0.4" 24 | }, 25 | "devDependencies": { 26 | "esbuild": "^0.19.11", 27 | "fs-extra": "^11.1.0", 28 | "lec": "^1.0.1", 29 | "line-ending-corrector": "^1.0.1", 30 | "live-server": "^1.1.0", 31 | "md5-file": "^5.0.0", 32 | "npm-run-all": "^4.1.5", 33 | "recursive-copy": "^2.0.11", 34 | "watch": "^1.0.2" 35 | }, 36 | "type": "module" 37 | } 38 | -------------------------------------------------------------------------------- /slides/readme.md: -------------------------------------------------------------------------------- 1 | # Slides for Rust fundamentals training 2 | 3 | ## Introduction 4 | 5 | This folder contains slides for the *Rust Fundamentals Training*. 6 | 7 | ## Usage 8 | 9 | ### Folders 10 | 11 | * The root folder should only contain settings files and this *readme.md* file. 12 | * [*favicon*](favicon) contains images and settings files for icons. 13 | * Your slide content should go in [*src*](src). 14 | 15 | ### VSCode 16 | 17 | This folder contains [recommended VSCode extensions](.vscode/extensions.json). We recommend to install them to get the best editing experience. 18 | 19 | ## Building 20 | 21 | To build the slides, run `npm run build`. You will get the built slide deck in the *dist* folder. Copy the *dist* folder to any static web server (e.g. GitHub) to host the slides. 22 | 23 | If you want to build slides in *watch* mode, run `npm run watch-build`. 24 | 25 | `npm run clean` will delete the *dist* folder and therefore gives you a clean environment for your next build if you need that. 26 | 27 | Do you miss anything (e.g. a *reveal.js* plugin, some files from *src* are not copied)? Customize the build process in the scripts in [*util*](util). 28 | 29 | ## View Slides Locally 30 | 31 | To start a webserver serving and watching the *dist* folder, run `npm run watch-start`. 32 | 33 | If you want to watch source files and have a local webserver for auto-refresh for the slides, run `npm start`. This script runs the scripts `watch-build` and `watch-start` in parallel. 34 | -------------------------------------------------------------------------------- /slides/src/chapter/002-hands-on-labs.md: -------------------------------------------------------------------------------- 1 | # Hands-on-Labs 2 | 3 | [https://github.com/rust-linz/rust-fundamentals-training/tree/main/hands-on-labs](https://github.com/rust-linz/rust-fundamentals-training/tree/main/hands-on-labs) 4 | 5 | --- 6 | 7 | ## Goal 8 | 9 | > Practice Rust concepts covered in the training in a larger end-to-end sample 10 | 11 | - Library with data structures and logic 12 | - Command-line app (CLI) 13 | - Web API with JS frontend 14 | - Rust in the browser via WASM 15 | 16 | --- 17 | 18 | ## Topic 19 | 20 | - Build various flavors of the classical [Battleship game](https://en.wikipedia.org/wiki/Battleship_(game)) 21 | - Simplified game logic: Single human player plays against the computer 22 | - Computer places ships randomly 23 | - Goal: Sink ships with as few shots as possible 24 | 25 | --- 26 | 27 | ## Prerequisites 28 | 29 | - [Rust](https://www.rust-lang.org/tools/install) 30 | - [Visual Studio Code](https://code.visualstudio.com) with the extensions 31 | - Extensions: *rust-analyzer* and *CodeLLDB* 32 | - [Node.js](https://nodejs.org/en/) (for web UI) 33 | 34 | --- 35 | 36 | ## Exercise Mode 37 | 38 | - Focus on concepts 39 | - Trainer will show samples and walk you through the code 40 | - Try to understand concepts, ask questions 41 | - Limited hands-on experience 42 | - Start with empty folder, build sample by copying code from GitHub step-by-step 43 | - Follow along with trainer, try to understand code and concepts 44 | - Existing Rust knowledge 45 | - Copy just unit tests 46 | - Try to implement solution on your own 47 | -------------------------------------------------------------------------------- /slides/src/chapter/005-cargo.md: -------------------------------------------------------------------------------- 1 | # Cargo 2 | 3 | ## The Rust Package Manager 4 | 5 | - [See separate slide deck](https://rstropek.github.io/CargoIntro/) 6 | - [Project on GitHub](https://github.com/rstropek/CargoIntro) 7 | -------------------------------------------------------------------------------- /slides/src/chapter/030-handling-errors.md: -------------------------------------------------------------------------------- 1 | # Handling Errors 2 | 3 | --- 4 | 5 | > Make impossible states impossible 6 | 7 | --- 8 | 9 | - In Rust, there is no `null` or `undefined` 10 | - Bindings either have a value, or they don't. 11 | - The `Option` enum helps! 12 | 13 | 14 | --- 15 | 16 | ```rust 17 | let file_name = get_file_name_setting() // Option 18 | 19 | match file_name { 20 | Some(x) => println!("{}", x), 21 | None => println!("No filename, stored!") 22 | }; 23 | ``` 24 | 25 | --- 26 | 27 | ```rust 28 | let file_name = get_file_name_setting() // Option 29 | 30 | let file_handle = File::open(file_name.unwrap_or("username.txt")); 31 | 32 | ``` 33 | 34 | --- 35 | 36 | - In Rust, you don't have regular exception handling 37 | - The Result type either has a result, or it has an error! 38 | 39 | --- 40 | 41 | ```rust 42 | let f = File::open(path); 43 | 44 | let mut f = match f { 45 | Ok(file) => file, 46 | Err(e) => return Err(e), 47 | }; 48 | ``` 49 | 50 | --- 51 | 52 | Since constant error handling can get tedious, we can propagate error to 53 | the function above 54 | 55 | ```rust 56 | let mut f = File::open(path)?; 57 | ``` 58 | 59 | -------------------------------------------------------------------------------- /slides/src/chapter/040-traits.md: -------------------------------------------------------------------------------- 1 | # Traits 2 | ## The meat of Rust 3 | 4 | --- 5 | 6 | ## What are traits 7 | 8 | - Traits define shared behaviour between types 9 | - Behaviour is defined in an abstract way 10 | - Traits are somewhat similar to interfaces in other programming languages 11 | - This does not mean Rust has traditional OO! There are differences 12 | 13 | --- 14 | 15 | ## Defining a trait 16 | 17 | ```rust 18 | struct Project { ... } 19 | struct MaintenanceHours { ... } 20 | 21 | pub trait Billable { 22 | fn bill(&self) -> f32; 23 | } 24 | ``` 25 | 26 | --- 27 | 28 | ## Implementing a trait 29 | 30 | ```rust 31 | impl Billable for MaintenanceHours { 32 | fn bill(&self) -> f32 { 33 | self.hours * self.rate 34 | } 35 | } 36 | ``` 37 | 38 | --- 39 | 40 | ## Default implementations 41 | 42 | ```rust 43 | pub trait Billable { 44 | fn bill(&self) -> f32 { 45 | 0f32 46 | } 47 | } 48 | ``` 49 | 50 | --- 51 | 52 | ## Coherence rule 53 | 54 | > You either own the type or own the trait 55 | 56 | - It's not possible to implement a trait you don't own for a type you don't own 57 | - You can implement your traits for your types 58 | - You can implement your traits for other types 59 | - You can implement foreign traits for your types 60 | 61 | --- 62 | 63 | 64 | ```rust 65 | impl fmt::Display for MaintenanceHours { 66 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 67 | write!(f, "{} hours à {} USD", self.hours, self.rate) 68 | } 69 | } 70 | ``` 71 | 72 | --- 73 | 74 | 75 | ## Trait bounds 76 | 77 | - Trait bounds allow a function to only accept types that implement a certain trait 78 | 79 | ```rust 80 | fn print_bill(bill: &impl Billable) {} 81 | 82 | fn print_bill(bill: &T) {} 83 | ``` 84 | -------------------------------------------------------------------------------- /slides/src/chapter/043-generics.md: -------------------------------------------------------------------------------- 1 | # Generics 2 | 3 | --- 4 | 5 | - Generics are very similar to other programming languages in syntax 6 | - ... but they mostly work on a type system level (class system !== type system) 7 | 8 | --- 9 | 10 | ## Generic annotation 11 | 12 | ```rust 13 | struct Point { 14 | x: T, 15 | y: T, 16 | } 17 | 18 | fn main() { 19 | let wont_work = Point { x: 5, y: 4 }; 20 | } 21 | ``` 22 | 23 | --- 24 | 25 | ## Trait bounds 26 | 27 | ```rust 28 | fn print_example(val: T) { 29 | println!("{}", val); 30 | } 31 | ``` 32 | 33 | - Trait bounds ensure that types are implementing certain traits 34 | 35 | --- 36 | 37 | ## Multiple trait bounds 38 | 39 | ```rust 40 | fn print_example(val: T) { 41 | println!("{}", val); 42 | } 43 | ``` 44 | 45 | - **Compound trait bound** - must satisify both `Display` and `Copy` 46 | 47 | --- 48 | 49 | ## Traitbounds with lifetimes 50 | 51 | ```rust 52 | fn print_example(val: &T) {...} 53 | ``` 54 | 55 | - Must satisfy `Display` and `Copy`, must outlive `'a` 56 | 57 | --- 58 | 59 | ## where Snytax 60 | 61 | ```rust 62 | fn print_example(val :T) 63 | where T: Display + Copy { 64 | 65 | } 66 | ``` -------------------------------------------------------------------------------- /slides/src/chapter/060-rocket.md: -------------------------------------------------------------------------------- 1 | # Rocket 2 | 3 | ## Building Web APIs with 🚀 Rocket 4 | 5 | --- 6 | 7 | ## Web Server Framework 8 | 9 | - Lots of frameworks for web development ([see more](https://github.com/rust-unofficial/awesome-rust#web-programming)) 10 | - We use [Rocket](https://rocket.rs) 11 | - Fast and simple (by using powerful macros) 12 | - Type safe 13 | - Less boilerplate 14 | - Extensible (based on Traits) 15 | 16 | --- 17 | 18 | ## Step-by-Step Example 19 | 20 | > In separate [GitHub repository](https://github.com/rstropek/RustyRockets) 21 | 22 | - Learn features of Rocket based on series of small samples 23 | - Already based on v0.5 (at the time of writing available as RC 1) 24 | - Use VSCode snippets to walk through the sample 25 | 26 | --- 27 | 28 | ## Included Features 29 | 30 | - [Routing](https://rocket.rs/v0.5-rc/guide/overview/#routing) (based on macros and parameter types) 31 | - With [dynamic paths](https://rocket.rs/v0.5-rc/guide/requests/#dynamic-paths) 32 | - With [query strings](https://rocket.rs/v0.5-rc/guide/requests/#query-strings) 33 | - [Async routes](https://rocket.rs/v0.5-rc/guide/overview/#async-routes) 34 | - [Request guards](https://rocket.rs/v0.5-rc/guide/requests/#request-guards) for e.g. security checks, extracting [cookies](https://rocket.rs/v0.5-rc/guide/requests/#cookies), etc. 35 | - Support for different body [formats](https://rocket.rs/v0.5-rc/guide/requests/#format) (e.g. JSON, [forms](https://rocket.rs/v0.5-rc/guide/requests/#forms)) 36 | - [Error catcher](https://rocket.rs/v0.5-rc/guide/requests/#error-catchers) (e.g. 404 error) 37 | - Extensible [response encoding](https://rocket.rs/v0.5-rc/guide/responses/) 38 | 39 | --- 40 | 41 | ## Included Features (continued) 42 | 43 | - [Managed state](https://rocket.rs/v0.5-rc/guide/state/) 44 | - With support for various [databases](https://rocket.rs/v0.5-rc/guide/state/#databases) included 45 | - [Fairings](https://rocket.rs/v0.5-rc/guide/fairings/) (similar to middlewares in other platforms) 46 | - Possibilities to implement [integration tests](https://rocket.rs/v0.5-rc/guide/testing/) 47 | - Support for mocking state management data types 48 | -------------------------------------------------------------------------------- /slides/src/example.md: -------------------------------------------------------------------------------- 1 | # Example Content 2 | 3 | A little bit of example content 4 | 5 | 6 | ## Special Chars and Fragments 7 | 8 | * Regular test 9 | * Test with Ümläuten 10 | * Test with some Emoji 11 | * 😅 12 | * 👍💪 13 | 14 | 15 | ## Table 16 | 17 | | ID | Description | 18 | | --- | ------------------- | 19 | | 1 | The quick brown fox | 20 | 21 | 22 | ## Code 23 | 24 | * Code with stepwise highlighting 25 | 26 | ```javascript [1,3|2,4] 27 | const x = 42; 28 | const y = 42; 29 | if (x === y) { 30 | console.log('Equal'); 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /slides/src/images/cd-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-linz/rust-fundamentals-training/9033deff4103b96bbc0f0749a9eb4fbaed0a1825/slides/src/images/cd-logo.png -------------------------------------------------------------------------------- /slides/src/images/rust-containers.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-linz/rust-fundamentals-training/9033deff4103b96bbc0f0749a9eb4fbaed0a1825/slides/src/images/rust-containers.jpeg -------------------------------------------------------------------------------- /slides/src/images/rustlove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-linz/rust-fundamentals-training/9033deff4103b96bbc0f0749a9eb4fbaed0a1825/slides/src/images/rustlove.png -------------------------------------------------------------------------------- /slides/src/index.css: -------------------------------------------------------------------------------- 1 | @import "../node_modules/reveal.js/dist/reveal.css"; 2 | @import "../node_modules/reveal.js/dist/theme/white.css"; 3 | @import "../node_modules/reveal.js/plugin/highlight/monokai.css"; 4 | 5 | .reveal { 6 | font-size: 32px; 7 | } 8 | -------------------------------------------------------------------------------- /slides/src/index.js: -------------------------------------------------------------------------------- 1 | import Reveal from 'reveal.js'; 2 | import markdown from 'reveal.js/plugin/markdown/markdown'; 3 | import highlight from 'reveal.js/plugin/highlight/highlight'; 4 | import notes from 'reveal.js/plugin/notes/notes'; 5 | import zoom from 'reveal.js/plugin/zoom/zoom'; 6 | import search from 'reveal.js/plugin/search/search'; 7 | 8 | let deck = new Reveal({ 9 | plugins: [markdown, highlight, notes, zoom, search] 10 | }) 11 | 12 | deck.initialize({ 13 | hash: true 14 | }); 15 | -------------------------------------------------------------------------------- /slides/util/build.js: -------------------------------------------------------------------------------- 1 | import { join } from "path"; 2 | import pkgEsbuild from "esbuild"; 3 | const { build } = pkgEsbuild; 4 | import copy from "recursive-copy"; 5 | 6 | (async () => { 7 | // Copy HTML and Markdown files 8 | var options = { 9 | overwrite: true, 10 | dot: true, 11 | filter: [ 12 | "*.html", 13 | "**/*.md", 14 | "**/*.png", 15 | "**/*.jpg", 16 | "**/*.jpeg", 17 | "**/*.svg", 18 | "**/*.gif", 19 | ], 20 | }; 21 | await copy("src", "dist", options); 22 | 23 | // Copy favicon-related files 24 | await copy("favicon", "dist", { overwrite: true }); 25 | 26 | // Build JS bundle 27 | build({ 28 | entryPoints: [join("src", "index.js")], 29 | bundle: true, 30 | minify: true, 31 | target: ["es2020"], 32 | sourcemap: true, 33 | outfile: join("dist", "index.js"), 34 | }); 35 | 36 | // Build CSS bundle (includes copying of fonts) 37 | build({ 38 | entryPoints: [join("src", "index.css")], 39 | bundle: true, 40 | minify: true, 41 | loader: { 42 | ".eot": "base64", 43 | ".ttf": "base64", 44 | ".woff": "base64", 45 | }, 46 | outfile: join("dist", "index.css"), 47 | }); 48 | 49 | console.log("Built ", new Date().toISOString()); 50 | })(); 51 | -------------------------------------------------------------------------------- /slides/util/clean.js: -------------------------------------------------------------------------------- 1 | import fsPkg from 'fs-extra'; 2 | const { rmdir } = fsPkg; 3 | 4 | (async () => { 5 | await rmdir('dist', { recursive: true }); 6 | })(); 7 | 8 | 9 | --------------------------------------------------------------------------------