├── .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 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
6 |
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 | 
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.
--------------------------------------------------------------------------------