├── .gitignore ├── src ├── chapter_0_examples │ ├── hello_world │ │ ├── .gitignore │ │ ├── src │ │ │ └── main.rs │ │ ├── Cargo.lock │ │ └── Cargo.toml │ └── hello_world_with_tests │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── src │ │ └── main.rs │ │ └── Cargo.toml ├── chapter_1_examples │ └── hello_you │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── src │ │ └── lib.rs │ │ └── Cargo.toml ├── SUMMARY.md ├── learning │ ├── arrays │ │ └── bin │ │ │ ├── tests.rs │ │ │ └── main.rs │ ├── ownership │ │ ├── notes.md │ │ └── bin │ │ │ └── main.rs │ ├── fizz_buzz │ │ └── bin │ │ │ └── main.rs │ └── hello_world │ │ └── bin │ │ └── hello_world.rs ├── chapter_1.md └── chapter_0.md ├── book.toml ├── Cargo.lock ├── .idea ├── $CACHE_FILE$ ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── vcs.xml ├── misc.xml ├── .gitignore ├── ClojureProjectResolveSettings.xml ├── modules.xml └── learn_rust_with_tests.iml ├── mdbook.sh ├── mdbook └── Dockerfile ├── CHAPTER_TEMPLATE.md ├── .github ├── ISSUE_TEMPLATE │ └── chapter-proposal.md └── workflows │ └── main.yml ├── Cargo.toml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | target -------------------------------------------------------------------------------- /src/chapter_0_examples/hello_world/.gitignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /src/chapter_1_examples/hello_you/.gitignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /src/chapter_0_examples/hello_world_with_tests/.gitignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /src/chapter_0_examples/hello_world/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Chapter 0: Hello, world](./chapter_0.md) 4 | - [Chapter 1: Hello, YOU](./chapter_1.md) 5 | -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["David Wickes", "Chris James"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Learn Rust With TDD" 7 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "learn_rust_with_tests" 5 | version = "0.1.0" 6 | 7 | -------------------------------------------------------------------------------- /.idea/$CACHE_FILE$: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /src/chapter_1_examples/hello_you/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "hello_you" 5 | version = "0.1.0" 6 | 7 | -------------------------------------------------------------------------------- /src/chapter_0_examples/hello_world/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "hello_world" 5 | version = "0.1.0" 6 | 7 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/ClojureProjectResolveSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | IDE 5 | 6 | -------------------------------------------------------------------------------- /src/chapter_0_examples/hello_world_with_tests/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "hello_world_with_tests" 5 | version = "0.1.0" 6 | 7 | -------------------------------------------------------------------------------- /mdbook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | name=mdbook-$RANDOM 6 | 7 | echo -n building docker image... 8 | docker build -qt $name mdbook >/dev/null 9 | echo done. 10 | 11 | docker run -it --init -v $PWD:$PWD -w $PWD -u $(id -u) --rm -p 3000:3000 -p 3001:3001 --name $name $name "$@" -------------------------------------------------------------------------------- /src/chapter_1_examples/hello_you/src/lib.rs: -------------------------------------------------------------------------------- 1 | fn greet() -> String { 2 | String::from("Hello, World!") 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::greet; 8 | 9 | #[test] 10 | fn test_greet() { 11 | assert_eq!("Hello, World!", greet()); 12 | } 13 | } -------------------------------------------------------------------------------- /src/chapter_1_examples/hello_you/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello_you" 3 | version = "0.1.0" 4 | authors = ["Chris James "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /mdbook/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.38.0 2 | RUN apt update && apt install -y curl && cd /tmp && \ 3 | curl -fsSLO https://github.com/rust-lang/mdBook/releases/download/v0.3.7/mdbook-v0.3.7-x86_64-unknown-linux-gnu.tar.gz && \ 4 | tar xzf mdbook*.tar.gz -C /usr/local/bin 5 | ENTRYPOINT [ "/usr/local/bin/mdbook" ] 6 | -------------------------------------------------------------------------------- /src/chapter_0_examples/hello_world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello_world" 3 | version = "0.1.0" 4 | authors = ["David Wickes "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CHAPTER_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Chapter template 2 | 3 | Some intro 4 | 5 | ## Write the test first 6 | ## Try to run the test 7 | ## Write the minimal amount of code for the test to run and check the failing test output 8 | ## Write enough code to make it pass 9 | ## Refactor 10 | 11 | ## Repeat for new requirements 12 | ## Wrapping up -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/chapter_0_examples/hello_world_with_tests/src/main.rs: -------------------------------------------------------------------------------- 1 | fn greet() -> String { 2 | String::from("Hello, World!") 3 | } 4 | 5 | fn main() { 6 | println!("{}", greet()); 7 | } 8 | 9 | #[cfg(test)] 10 | mod tests { 11 | use super::greet; 12 | 13 | #[test] 14 | fn test_greet() { 15 | assert_eq!(greet(), "Hello, world!"); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/chapter_0_examples/hello_world_with_tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello_world_with_tests" 3 | version = "0.1.0" 4 | authors = [ 5 | "David Wickes ", 6 | "Tom Yandell ", 7 | ] 8 | edition = "2018" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | -------------------------------------------------------------------------------- /src/learning/arrays/bin/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::{sum3, sum4, sum5, sum1, sum2}; 2 | 3 | const NUMBERS: [i32; 3] = [1, 2, 3]; 4 | const EXPECTED_SUM: i32 = 6; 5 | 6 | fn test_sum(f: fn([i32; 3]) -> i32) { 7 | assert_eq!(EXPECTED_SUM, f(NUMBERS)) 8 | } 9 | 10 | #[test] 11 | fn test_sums() { 12 | test_sum(sum1); 13 | test_sum(sum2); 14 | test_sum(sum3); 15 | test_sum(sum4); 16 | test_sum(sum5); 17 | } 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/chapter-proposal.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Chapter proposal 3 | about: Describe a new chapter 4 | title: Chapter [X] 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Title 11 | 12 | ## Purpose 13 | 14 | ## Prerequisites 15 | 16 | Other chapters / concepts that are needed to understand this chapter 17 | 18 | ## List of things to be covered 19 | 20 | ## How will it progress through the TDD cycle? 21 | -------------------------------------------------------------------------------- /src/learning/ownership/notes.md: -------------------------------------------------------------------------------- 1 | # notes 2 | 3 | usual stack heap la de da should be discussed (or just link to Rust book) 4 | 5 | `str` = always lives on stack, immutable (fixed size suitable for stack), known at compile time 6 | 7 | Problem is you don't always know what strings go into your program (user input, API calls, etc etc) 8 | 9 | `String`, stored on heap. Suitable for strings unknown at compile time 10 | 11 | `let s = String::from("hello");` -------------------------------------------------------------------------------- /src/learning/fizz_buzz/bin/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | fizz_buzz("fizz", "buzz", 3, 5) 3 | } 4 | 5 | fn fizz_buzz(fizz_word: &str, buzz_word: &str, fizz_n: i32, buzz_n: i32) { 6 | for i in 1..100 { 7 | match (i% fizz_n, i% buzz_n) { 8 | (0, 0) => println!("{0}{1}", fizz_word, buzz_word), 9 | (0, _) => println!("{}", fizz_word), 10 | (_, 0) => println!("{}", buzz_word), 11 | (_, _) => println!("{}", i) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.idea/learn_rust_with_tests.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-18.04 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Setup mdBook 15 | uses: peaceiris/actions-mdbook@v1 16 | with: 17 | mdbook-version: 'latest' 18 | 19 | - run: mdbook build 20 | 21 | - name: Deploy 22 | uses: peaceiris/actions-gh-pages@v3 23 | with: 24 | github_token: ${{ secrets.GITHUB_TOKEN }} 25 | publish_dir: ./book 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "learn_rust_with_tests" 3 | version = "0.1.0" 4 | authors = ["Chris James "] 5 | edition = "2018" 6 | 7 | [[bin]] 8 | name = "hello_world" 9 | path = "src/learning/hello_world/bin/hello_world.rs" 10 | 11 | [[bin]] 12 | name = "arrays" 13 | path = "src/learning/arrays/bin/main.rs" 14 | 15 | [[bin]] 16 | name = "fizz_buzz" 17 | path = "src/learning/fizz_buzz/bin/main.rs" 18 | 19 | [[bin]] 20 | name = "ownership" 21 | path = "src/learning/ownership/bin/main.rs" 22 | 23 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 24 | 25 | [dependencies] -------------------------------------------------------------------------------- /src/chapter_1.md: -------------------------------------------------------------------------------- 1 | # Chapter 1: Hello, You! 2 | 3 | In the previous chapter we wrote a greet function and _then_ wrote the tests. 4 | 5 | ```rust 6 | {{#include ./chapter_1_examples/hello_you/src/lib.rs}} 7 | ``` 8 | 9 | From this point on we will be writing tests first. 10 | 11 | Our next requirement is to let us specify the recipient of the greeting. 12 | 13 | Let's start by capturing these requirements in a test. This is basic test driven development and allows us to make sure our test is actually testing what we want. When you retrospectively write tests there is the risk that your test may continue to pass even if the code doesn't work as intended. 14 | 15 | ## Write the test first 16 | ## Try to run the test 17 | ## Write the minimal amount of code for the test to run and check the failing test output 18 | ## Write enough code to make it pass 19 | ## Refactor -------------------------------------------------------------------------------- /src/learning/arrays/bin/main.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Add; // note this trait (i think?) needs importing 2 | 3 | fn main() { 4 | println!("{}", sum3([1, 1, 1])) 5 | } 6 | 7 | fn sum1(numbers: [i32; 3]) -> i32 { 8 | let mut total = 0; 9 | let mut counter = 0; 10 | loop { // todo: use loop as an expression 11 | if counter == 3 { 12 | break 13 | } 14 | total = total + numbers[counter]; 15 | counter = counter + 1; 16 | } 17 | total 18 | } 19 | 20 | fn sum2(numbers: [i32; 3]) -> i32 { 21 | let mut total = 0; 22 | let mut counter = 0; 23 | while counter != 3 { 24 | total = total + numbers[counter]; 25 | counter = counter + 1; 26 | } 27 | total 28 | } 29 | 30 | fn sum3(numbers: [i32; 3]) -> i32 { 31 | let mut total = 0; 32 | for (_, &item) in numbers.iter().enumerate() { // for prevents bugs with magic number 3 if length is changed, reads better etc 33 | total = total + item 34 | } 35 | total 36 | } 37 | 38 | fn sum4(numbers: [i32; 3]) -> i32 { 39 | numbers 40 | .iter() 41 | .enumerate() 42 | .fold(0, |sum, val| val.1.add(sum)) // this feels wonky but fine 43 | } 44 | 45 | fn sum5(numbers: [i32; 3]) -> i32 { 46 | numbers.iter().sum() 47 | } 48 | 49 | #[cfg(test)] 50 | mod tests; 51 | 52 | /* 53 | notes 54 | 55 | use this crate for reduce https://docs.rs/reduce/0.1.2/reduce/ 56 | */ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learn Rust with TDD 2 | 3 | Like Learn Go with Tests, but in Rust. 4 | 5 | [HTML](https://learn-with-tests.github.io/learn-rust-with-tests/) 6 | 7 | ![github pages](https://github.com/learn-with-tests/learn-rust-with-tests/workflows/github%20pages/badge.svg) 8 | 9 | [Buy us a coffee :coffee:](https://www.buymeacoffee.com/learnwithtests)! 10 | 11 | ## Development Requirements 12 | 13 | _Learn Rust With TDD_ is written using [mdBook][mdBook]. Follow the instructions 14 | there to install mdBook on your machine and build the book. 15 | 16 | ## Things to improve 17 | 18 | - ~Make some £££~ Provide this amazing resource to the community free of charge 19 | - Maybe use https://github.com/rust-lang/mdBook 20 | - My spelling and grammar is shit. It would be good to automate a checker 21 | - Automate building the book 22 | - A big pain point with the go one was keeping snippets in the md and the source consistent, is there a way to make it easy? (https://rust-lang.github.io/mdBook/format/mdbook.html#mdbook-specific-markdown) 23 | 24 | ## Resources 25 | 26 | - https://doc.rust-lang.org/book/ch11-00-testing.html 27 | 28 | [mdBook]: https://github.com/rust-lang/mdBook 29 | 30 | ## Runing mdbook 31 | 32 | To run mdbook via docker from the root of the project: 33 | 34 | ```shell 35 | # show usage 36 | ./mdbook.sh 37 | 38 | # test code samples 39 | ./mdbook.sh test 40 | 41 | # serve the book at http://0.0.0.0:3000, watching for changes 42 | ./mdbook.sh serve --hostname 0.0.0.0 43 | 44 | # build the book to the book/ folder 45 | ./mdbook.sh build 46 | ``` 47 | -------------------------------------------------------------------------------- /src/learning/ownership/bin/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // String::from allocates on heap, as size is unknown etc etc 3 | // Rust will free s1 when it is out of scope automagically 4 | let mut s1 = String::from("yo"); 5 | s1.push_str(" lol"); 6 | println!("{}", s1); 7 | 8 | // by doing this, rust considers s1 no longer valid, so we don't accidentally free the same bit of memory twice 9 | // this is called a _move_ 10 | // so we're not doing a copy here, simply moving the reference to the stuff on the heap 11 | let s2 = s1; 12 | // println!("{}", s1); <- so this won't compile 13 | println!("{}", s2); 14 | 15 | // if we do want to copy, we can clone (this is nice vs other langs as it's explicit, potentially expensive) 16 | let s3 = s2.clone(); 17 | println!("s2 = {0}, s3 = {1}", s2, s3); 18 | 19 | // ownership also happens across functions, so once we call take ownership we no longer have access to s3 20 | take_ownership(s3); 21 | // println!("s3 after ownership {}", s3); // error[E0382]: borrow of moved value: `s3` 22 | 23 | // referencing and borrowing 24 | let s4 = String::from("lets get referencing and borrowing"); 25 | borrow(&s4); // & for ref to a thing 26 | println!("i still own s4 coz i can print it {}", s4); 27 | 28 | 29 | let mut s5 = String::from("mutable bootable"); 30 | // let s6 = &mut s5; //can only have one mut ref per scope. Prevents data races 31 | mutable_borrow(&mut s5); 32 | println!("{0}", s5) 33 | } 34 | 35 | fn take_ownership(x: String) { 36 | println!("haha i own you {}", x); 37 | } 38 | 39 | // taking a reference is called borrowing 40 | fn borrow(x: &String) { 41 | // x.push_str("poop"); // references are immutable by default 42 | println!("i don't own you {}", x) 43 | } 44 | 45 | fn mutable_borrow(x: &mut String) { 46 | x.push_str("poop") 47 | } -------------------------------------------------------------------------------- /src/learning/hello_world/bin/hello_world.rs: -------------------------------------------------------------------------------- 1 | use crate::Language::{English, Spanish, French}; 2 | 3 | fn main() { 4 | println!("{}", greet(English,None)); 5 | println!("{}", greet(Spanish,Option::from("Pepper"))); 6 | println!("{}", greet(French,Option::from("Lisa"))); 7 | } 8 | 9 | // refactor to and talk about: type safety, descriptiveness, caller doesn't need magic strings 10 | enum Language { 11 | English, 12 | Spanish, 13 | French, 14 | } 15 | 16 | // refactor to and talk about: constants 17 | const DEFAULT_GREET: &str = "World"; 18 | 19 | // refactor to and talk about: option 20 | fn greet(lang: Language, name: Option<&str>) -> String { 21 | // type safety around pattern matching 22 | let greeting = match lang { 23 | Language::English => "Hello", 24 | Language::Spanish => "Hola", 25 | Language::French => "Bonjour", 26 | }; 27 | // formatting strings, dealing with Some vs None 28 | format!("{0}, {1}!", greeting, name.unwrap_or(DEFAULT_GREET)) 29 | } 30 | 31 | // talk about: separate modules for tests so they're not compiled into the program 32 | #[cfg(test)] 33 | mod tests { 34 | use super::greet; 35 | use crate::Language::{Spanish, English, French}; 36 | 37 | #[test] 38 | fn test_greet() { 39 | assert_eq!("Hello, Chris!", greet(English, Option::from("Chris"))); 40 | assert_eq!("Hola, Dave!", greet(Spanish, Option::from("Dave"))); 41 | assert_eq!("Bonjour, Ruth!", greet(French, Option::from("Ruth"))); 42 | } 43 | } 44 | 45 | 46 | /* notes 47 | - Hooray for option back in my life 48 | - str vs String eugh, https://www.ameyalokare.com/rust/2017/10/12/rust-str-vs-String.html 49 | - str = immutable, fixed length string in memory 50 | - String = growable, on heap 51 | 52 | You can only ever interact with str as a borrowed type aka &str. This is called a string slice, an immutable view of a string. This is the preferred way to pass strings around, as we shall see. 53 | 54 | _The only real use case I can think of is if you want to pass a mutable reference to a function that needs to modify the string_: 55 | */ -------------------------------------------------------------------------------- /src/chapter_0.md: -------------------------------------------------------------------------------- 1 | # Chapter 0: Hello, world! 2 | 3 | In this chapter, as is traditional, we'll introduce Rust by printing "Hello, 4 | world!" to the screen. Along the way we'll look at how to set up a Rust project 5 | using Cargo, what the files 6 | 7 | Let's start a new Rust project. If we've already installed Rust and Cargo, this 8 | is as simple as 9 | 10 | ```sh 11 | $ cargo init hello_world 12 | ``` 13 | 14 | at the command line. 15 | 16 | This will create a new directory, `hello_world`. The structure within this 17 | folder looks like this: 18 | 19 | ```console 20 | hello_world 21 | ├── Cargo.toml 22 | └── src 23 | └── main.rs 24 | ``` 25 | 26 | `Cargo.toml` contains metadata about the project: 27 | 28 | ```toml 29 | {{#include ./chapter_0_examples/hello_world/Cargo.toml:1:5}} 30 | ``` 31 | 32 | This includes information like the name of the package, the authors, which 33 | version of Rust is being used, and any external Rust libraries - crates - that 34 | we're using. We'll look at that in a bit more detail later. 35 | 36 | The `src` directory contains all the Rust code that we're going to write - the 37 | source code! And the `main.rs` file inside `src` contains our as yet unwritten 38 | program. Cargo puts a dummy program in there to get us started. 39 | 40 | ```rust 41 | {{#include ./chapter_0_examples/hello_world/src/main.rs}} 42 | ``` 43 | 44 | Well, blimey! Looks like Cargo has done all the hard work for us. 45 | 46 | To run this program we need to _compile_ the Rust code into something that our 47 | computer can execute - a file called a _binary_ - using a program called 48 | a _compiler_. 49 | 50 | Cargo provides a nice interface to the Rust compiler through the command 51 | 52 | ```sh 53 | $ cargo build 54 | ``` 55 | 56 | when we run that inside the `hello_world` directory we see some output: 57 | 58 | ```sh 59 | Compiling hello_world v0.1.0 (/Users/davidwic/dev/personal/... 60 | Finished dev [unoptimized + debuginfo] target(s) in 0.40s 61 | ``` 62 | 63 | and a new directory called `target` should appear in your project directory. 64 | This directory contains all the results of compiling your very simple program - 65 | a surprisingly large number of files which are 'intermediate' steps, as well as 66 | a binary file called, unsurprisingly, `hello_world`. 67 | 68 | To run our program we need to execute the binary. We can do this on the command 69 | line like so: 70 | 71 | ```sh 72 | $ ./target/debug/hello_world 73 | ``` 74 | 75 | and we should then see: 76 | 77 | ```sh 78 | Hello, world! 79 | ``` 80 | 81 | Great success. 82 | 83 | To save performing all of the steps above, Cargo provides a command to both 84 | compile and run a program - which is often what we want to do: 85 | 86 | ```sh 87 | $ cargo run 88 | ``` 89 | 90 | Which should print out. 91 | 92 | ```sh 93 | Finished dev [unoptimized + debuginfo] target(s) in 0.01s 94 | Running `target/debug/hello_world` 95 | Hello, world! 96 | ``` 97 | 98 | ## But where are the tests? 99 | 100 | This is, after all, _Learn Rust With Tests_. Where are they? 101 | 102 | ... 103 | 104 | If we want to test the program that ~we~ Cargo wrote for us, we need something 105 | to test. This might sound obvious, but at the moment we don't really have 106 | something that we can have the computer test for us _automatically_. Our program 107 | is one, admittedly small, lump: 108 | 109 | ```rust 110 | {{#include ./chapter_0_examples/hello_world/src/main.rs}} 111 | ``` 112 | 113 | The first line we can see creates a new _function_ called `main`. The `fn` 114 | keyword declares the function, then we can see its name (`main`), and then we 115 | get a pair of paretheses where we'd put the names of the _arguments_ that the 116 | function recieves - think of these as the input. It's empty because main doesn't 117 | take any inputs! 118 | 119 | The `main` function in Rust is special as it's the 'entry point' for any Rust 120 | program. When your compiled program runs it starts by running the `main` 121 | function - this is sometimes called 'calling' or 'executing' the function. 122 | 123 | Then there's a curly brace `{`, which is the beginning of the _function body_. 124 | This is the meat of the function - what actually happens when the function runs. 125 | And in this case: 126 | 127 | ```rust 128 | {{#include ./chapter_0_examples/hello_world/src/main.rs:2:2}} 129 | ``` 130 | 131 | `println!` is not technically function - but it looks and acts like one! It's 132 | actually a _macro_. We'll look at macros much later. But for now we can think of 133 | it as a function. The function is being called by having a pair of paretheses 134 | after its name. And it's being called with one argument - one 'thing' inside the parentheses - a _string_ that says `"Hello, world!"`. 135 | 136 | Then comes a closing parenthesis and a semicolon (`;`). Lines of Rust code 137 | usually end in a semicolon - there's a special reason for this which we'll see 138 | later. And finally a closing curly brace `}`, which ends the function body. 139 | Whew! 140 | 141 | Right now we can only test this program by running it. Which isn't that bad, 142 | really. We can run it quickly and as often as we like, and we can read the 143 | output to see that it says what we expect. 144 | 145 | We could even write an automated test in another language - something that runs 146 | the program and compares what it outputs to what we expect. This type of test is 147 | sometimes called an _acceptance test_. We will look at those later too. 148 | 149 | We can only test our program this way because the output is the only _interface_ 150 | we have access to in the program. 151 | 152 | At the moment we're trying to test the steering wheel of a car by watching 153 | somebody else drive the car. Wouldn't it be easier if we just got inside the car 154 | and turned the wheel ourself? 155 | 156 | To do this we're going to introduce another surface - another interface - to our 157 | program. We're going to test it _from the inside_ of our program to check that 158 | it works. Then we're going to use that interface when the program actually runs 159 | in order to print "Hello, world!". 160 | 161 | Of course, this doesn't guarantee that our program will work. Just as climbing 162 | inside a stationary car, turning the wheel, and watching the wheels turn doesn't 163 | guarantee that the car will actually turn a corner when it's being driven. But 164 | it does give us some confidence that it will. 165 | 166 | ## My First Test - Rust Edition 167 | 168 | How do you test this? It is good to separate your "domain" code from the outside 169 | world (_side effects_). The `println!` is a side effect (printing to stdout) and 170 | the string we send in is our domain. 171 | 172 | So let's separate these concerns so it's easier to test. 173 | 174 | ```rust 175 | {{#include ./chapter_0_examples/hello_world_with_tests/src/main.rs:1:7}} 176 | ``` 177 | 178 | We have created a new function again with `fn`, but this time we've added the 179 | symbol `->` to introduce the type(s) that the function returns, plus the keyword 180 | `String` meaning our function returns a string (technically transfering 181 | _ownership_ of a heap allocated and growable vector of bytes representing a 182 | valid string encoded as UTF-8, but we'll come back to that!). The last 183 | expression in a function if it doesn't end with a semi-colon is used as the 184 | return value without needing the `return` keyword. This seems a bit random at 185 | first, but it will make more sense as we find out more about the language. For 186 | now we'll stick to this convention for very short functions. 187 | 188 | The `println!` macro takes a format string literal as the first argument, 189 | followed by zero or more values that are used by that format string. In our case 190 | the format string contains `{}` to embed the value returned from our `hello` 191 | function formatted simply as a string. 192 | 193 | Tests can be added to the same file: 194 | 195 | ```rust 196 | {{#include ./chapter_0_examples/hello_world_with_tests/src/main.rs:9:18}} 197 | ``` 198 | 199 | Before we delve into what's going on here let's run the tests: run `cargo test` 200 | from your terminal - you should see a successful test run with 1 passing test. 201 | 202 | The first line is a directive to the compiler meaning that the following item 203 | will only be compiled when running the tests. That item is a _sub-module_ - a 204 | module within the module defined by our `main.rs` file. We've followed the 205 | convention of calling our sub-module "tests", so it does what it says on the 206 | tin! Next the `use` statement is _used_ to bring the greet function into the 207 | scope of our sub-module. 208 | 209 | The function with the `#[test]` annotation is where the testing action begins. 210 | The annotation is used by the test runner to identify which function(s) should 211 | be run as tests. The 212 | [`assert_eq!`](https://doc.rust-lang.org/std/macro.assert_eq.html) macro is part 213 | of standard library for testing equality between two values. In this case we are 214 | asserting the actual value returned from our function matches the expected 215 | value. 216 | 217 | Notice that when it comes to testing Rust is a batteries included language - 218 | everything you need to get started is included in the language and standard 219 | tools. Also of note is that while there is a little boilerplate required to 220 | separate your testing code, test's are concise with very little ceremony 221 | required. --------------------------------------------------------------------------------