├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── SUMMARY.md ├── appveyor.yml ├── book.json ├── book ├── vol1 │ ├── README.md │ ├── day1.md │ ├── day10.md │ ├── day11.md │ ├── day12.md │ ├── day13.md │ ├── day14.md │ ├── day15.md │ ├── day16.md │ ├── day17.md │ ├── day18.md │ ├── day19.md │ ├── day2.md │ ├── day20.md │ ├── day21.md │ ├── day22.md │ ├── day23.md │ ├── day24.md │ ├── day3.md │ ├── day4.md │ ├── day5.md │ ├── day6.md │ ├── day7.md │ ├── day8.md │ └── day9.md └── vol2 │ ├── README.md │ ├── day1.md │ ├── day10.md │ ├── day11.md │ ├── day12.md │ ├── day13.md │ ├── day14.md │ ├── day15.md │ ├── day16.md │ ├── day17.md │ ├── day18.md │ ├── day19.md │ ├── day2.md │ ├── day20.md │ ├── day21.md │ ├── day22.md │ ├── day23.md │ ├── day24.md │ ├── day3.md │ ├── day4.md │ ├── day5.md │ ├── day6.md │ ├── day7.md │ ├── day8.md │ └── day9.md ├── deploy-book.sh ├── vol1 ├── Cargo.lock ├── Cargo.toml ├── data │ └── in.png └── src │ └── bin │ ├── day10.rs │ ├── day11.rs │ ├── day12.rs │ ├── day13.rs │ ├── day14.rs │ ├── day15.rs │ ├── day17.rs │ ├── day18.rs │ ├── day2.rs │ ├── day20.rs │ ├── day21.rs │ ├── day3.rs │ ├── day4.rs │ ├── day5.rs │ ├── day6.rs │ ├── day7.rs │ └── day9.rs └── vol2 ├── .env ├── Cargo.lock ├── Cargo.toml ├── data ├── playbook.yml └── schedule.json ├── migrations ├── .gitkeep ├── 20161206155312_initial_user │ ├── down.sql │ └── up.sql ├── 20161206160342_create_photos │ ├── down.sql │ └── up.sql └── 20161207095753_add_tags │ ├── down.sql │ └── up.sql ├── src └── bin │ ├── day10.rs │ ├── day11.rs │ ├── day12.rs │ ├── day13.rs │ ├── day14.rs │ ├── day15.rs │ ├── day16.rs │ ├── day17.rs │ ├── day18.rs │ ├── day19.rs │ ├── day2.rs │ ├── day20.rs │ ├── day21.rs │ ├── day22.rs │ ├── day3.rs │ ├── day4.rs │ ├── day5.rs │ ├── day6.rs │ ├── day7.rs │ ├── day8.rs │ ├── day9.rs │ └── lookups.rs └── templates ├── blog.html ├── config.ini └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .cargo 3 | out*.jpg 4 | out*.png 5 | _book/ 6 | node_modules/ 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - nightly 4 | cache: cargo 5 | addons: 6 | postgresql: "9.4" 7 | apt: 8 | packages: 9 | - cmake 10 | - cmake-data 11 | - libpq-dev 12 | services: 13 | - redis-server 14 | before_install: 15 | - sudo add-apt-repository ppa:chris-lea/libsodium -y 16 | - sudo add-apt-repository ppa:chris-lea/zeromq -y 17 | - sudo apt-get update 18 | - sudo apt-get install libfuse-dev libsodium-dev libzmq5-dev -y 19 | - nvm install v6 20 | before_script: 21 | - psql -c "create user rust with password 'rust';" -U postgres 22 | - psql -c "create database rust owner rust;" -U postgres 23 | - psql -c "create user diesel with password 'diesel';" -U postgres 24 | - psql -c "create database diesel owner diesel;" -U postgres 25 | - git config --global user.email "example@example.com" 26 | - git config --global user.name "24daysofrust" 27 | - cd .. && mkdir repo && cd repo && git init && echo "hello" > hello.txt && git add hello.txt && git commit -am "Initial commit" && cd .. 28 | - mkdir git_remote && cd git_remote && git init --bare && cd $TRAVIS_BUILD_DIR 29 | - export PATH=$PATH:$HOME/.cargo/bin/ 30 | - if ! command -v diesel > /dev/null; then cargo install diesel_cli; fi 31 | script: 32 | - cd vol1 33 | - cargo build 34 | - cargo run --bin=day2 35 | - cargo run --bin=day3 36 | # - cargo run --bin=day4 37 | - cargo run --bin=day5 38 | - cargo run --bin=day6 39 | - cargo run --bin=day7 40 | - cargo run --bin=day9 41 | - cargo run --bin=day10 42 | - cargo run --bin=day11 43 | - cargo run --bin=day12 44 | - cargo run --bin=day13 45 | - cargo run --bin=day14 46 | - cargo run --bin=day17 47 | - cargo run --bin=day18 48 | - cargo run --bin=day20 49 | - cargo run --bin=day21 50 | - cd ../vol2 51 | - diesel migration run 52 | - cargo build 53 | - cargo run --bin=day2 54 | - cargo run --bin=day3 55 | - cargo run --bin=day4 56 | - cargo run --bin=day5 57 | - cargo run --bin=day6 58 | - cargo run --bin=day7 59 | - cargo run --bin=day8 60 | - cargo run --bin=day10 61 | - cargo run --bin=day11 62 | - cargo run --bin=day12 63 | - cargo run --bin=day13 64 | # - cargo run --bin=day14 65 | - cargo run --bin=day15 66 | - cargo run --bin=day16 -- ../../repo 67 | - cargo run --bin=day17 68 | - cargo run --bin=day18 69 | - cargo run --bin=day19 70 | - cargo run --bin=day20 71 | - cargo run --bin=day21 72 | # - cargo run --bin=day22 73 | after_success: 74 | - cd .. 75 | - npm install gitbook-cli 76 | - gitbook install 77 | - gitbook build -v 2.6.7 78 | - test "$TRAVIS_PULL_REQUEST" == false && test "$TRAVIS_BRANCH" == "master" && bash deploy-book.sh 79 | env: 80 | global: 81 | - secure: "bwaHviVayREGvar1lk9CY7gZR/Ck95hY6TuKZUhrBXakg4k/uw4D6fHEbCb7NTom12mvD3UP7Spwp4nZDjkRmXIppODB+SYmkS9zxwvm154SH+09uXW4XCjbtIUWsGx79pG/FlVQ7Cz5kvY4IY+a6PV2JSsTBvJCYjeNrZ9Qeuo=" 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015 Zbigniew Siciarz 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 24daysofrust 2 | ============ 3 | 4 | [![Build Status](https://travis-ci.org/zsiciarz/24daysofrust.svg?branch=master)](https://travis-ci.org/zsiciarz/24daysofrust) 5 | [![Build status](https://ci.appveyor.com/api/projects/status/ys82q4b43td2mxb7?svg=true)](https://ci.appveyor.com/project/zsiciarz/24daysofrust) 6 | 7 | 8 | The "24 days of Rust" article series. 9 | 10 | Author 11 | ====== 12 | 13 | * Zbigniew Siciarz (zbigniew at siciarz dot net) 14 | 15 | License 16 | ======= 17 | 18 | This work is released under the MIT license. A copy of the license is provided 19 | in the LICENSE file. 20 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | Summary 2 | ======= 3 | 4 | * [Volume 1](book/vol1/README.md) 5 | * [Day 1 - Cargo and crates.io](book/vol1/day1.md) 6 | * [Day 2 - primal](book/vol1/day2.md) 7 | * [Day 3 - csv](book/vol1/day3.md) 8 | * [Day 4 - docopt](book/vol1/day4.md) 9 | * [Day 5 - hyper](book/vol1/day5.md) 10 | * [Day 6 - working with JSON](book/vol1/day6.md) 11 | * [Day 7 - itertools](book/vol1/day7.md) 12 | * [Day 8 - racer](book/vol1/day8.md) 13 | * [Day 9 - anymap](book/vol1/day9.md) 14 | * [Day 10 - the glorious tau](book/vol1/day10.md) 15 | * [Day 11 - postgres](book/vol1/day11.md) 16 | * [Day 12 - image](book/vol1/day12.md) 17 | * [Day 13 - uuid](book/vol1/day13.md) 18 | * [Day 14 - nalgebra](book/vol1/day14.md) 19 | * [Day 15 - FUSE filesystems, part 1](book/vol1/day15.md) 20 | * [Day 16 - FUSE filesystems, part 2](book/vol1/day16.md) 21 | * [Day 17 - from_fn](book/vol1/day17.md) 22 | * [Day 18 - redis](book/vol1/day18.md) 23 | * [Day 19 - rusti](book/vol1/day19.md) 24 | * [Day 20 - zeromq](book/vol1/day20.md) 25 | * [Day 21 - rust-crypto](book/vol1/day21.md) 26 | * [Day 22 - built with Rust](book/vol1/day22.md) 27 | * [Day 23 - calling Rust from other languages](book/vol1/day23.md) 28 | * [Day 24 - conclusion](book/vol1/day24.md) 29 | * [Volume 2](book/vol2/README.md) 30 | * [Day 1 - cargo subcommands](book/vol2/day1.md) 31 | * [Day 2 - hound](book/vol2/day2.md) 32 | * [Day 3 - rayon](book/vol2/day3.md) 33 | * [Day 4 - structured logging](book/vol2/day4.md) 34 | * [Day 5 - environment variables](book/vol2/day5.md) 35 | * [Day 6 - derive_builder](book/vol2/day6.md) 36 | * [Day 7 - static initialization](book/vol2/day7.md) 37 | * [Day 8 - serde](book/vol2/day8.md) 38 | * [Day 9 - winreg](book/vol2/day9.md) 39 | * [Day 10 - nom, part 1](book/vol2/day10.md) 40 | * [Day 11 - nom, part 2](book/vol2/day11.md) 41 | * [Day 12 - clap](book/vol2/day12.md) 42 | * [Day 13 - zip and lzma compression](book/vol2/day13.md) 43 | * [Day 14 - Cursive](book/vol2/day14.md) 44 | * [Day 15 - tera](book/vol2/day15.md) 45 | * [Day 16 - git2](book/vol2/day16.md) 46 | * [Day 17 - diesel](book/vol2/day17.md) 47 | * [Day 18 - error_chain](book/vol2/day18.md) 48 | * [Day 19 - leftpad](book/vol2/day19.md) 49 | * [Day 20 - reqwest](book/vol2/day20.md) 50 | * [Day 21 - app_dirs and preferences](book/vol2/day21.md) 51 | * [Day 22 - lettre](book/vol2/day22.md) 52 | * [Day 23 - built with Rust](book/vol2/day23.md) 53 | * [Day 24 - conclusion](book/vol2/day24.md) 54 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | OPENSSL_INCLUDE_DIR: C:\OpenSSL-Win64\include 3 | OPENSSL_LIB_DIR: C:\OpenSSL-Win64\lib 4 | OPENSSL_LIBS: ssleay32:libeay32 5 | 6 | cache: 7 | - 'C:\Users\appveyor\.cargo' 8 | 9 | install: 10 | - ps: Start-FileDownload 'https://static.rust-lang.org/dist/rust-nightly-x86_64-pc-windows-gnu.exe' 11 | - rust-nightly-x86_64-pc-windows-gnu.exe /VERYSILENT /NORESTART /DIR="C:\Program Files\Rust" 12 | - SET PATH=%PATH%;C:\Program Files\Rust\bin;C:\msys64\mingw64\bin;C:\OpenSSL-Win64 13 | - cc --version 14 | - gcc --version 15 | - rustc -V 16 | - cargo -V 17 | - nuget install redis-64 -excludeversion 18 | - redis-64\tools\redis-server.exe --service-install 19 | - redis-64\tools\redis-server.exe --service-start 20 | 21 | build: false 22 | 23 | test_script: 24 | - cd vol1 25 | - cargo build 26 | - cargo run --bin=day2 27 | - cargo run --bin=day3 28 | # - cargo run --bin=day4 29 | - cargo run --bin=day5 30 | - cargo run --bin=day6 31 | - cargo run --bin=day7 32 | - cargo run --bin=day9 33 | - cargo run --bin=day10 34 | - cargo run --bin=day11 35 | - cargo run --bin=day12 36 | - cargo run --bin=day13 37 | - cargo run --bin=day14 38 | - cargo run --bin=day17 39 | - cargo run --bin=day18 40 | - cargo run --bin=day20 41 | - cargo run --bin=day21 42 | - cd ..\vol2 43 | - cargo build 44 | - cargo run --bin=day2 45 | - cargo run --bin=day3 46 | - cargo run --bin=day4 47 | - cargo run --bin=day5 48 | - cargo run --bin=day6 49 | - cargo run --bin=day7 50 | - cargo run --bin=day8 51 | - cargo run --bin=day10 52 | - cargo run --bin=day11 53 | - cargo run --bin=day12 54 | - cargo run --bin=day13 55 | - cargo run --bin=day14 56 | - cargo run --bin=day15 57 | - cargo run --bin=day16 58 | - cargo run --bin=day17 59 | - cargo run --bin=day18 || ver>nul 60 | - cargo run --bin=day19 61 | - cargo run --bin=day20 62 | - cargo run --bin=day21 63 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "gitbook": "2.x.x", 3 | "title": "24 days of Rust", 4 | "plugins": [ 5 | "include-codeblock@1.9.0" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /book/vol1/README.md: -------------------------------------------------------------------------------- 1 | Volume 1 2 | ======== 3 | 4 | This is the first volume of *24 days of Rust* which was written in December 5 | 2014. 6 | -------------------------------------------------------------------------------- /book/vol1/day1.md: -------------------------------------------------------------------------------- 1 | # Day 1 - Cargo and crates.io 2 | 3 | Inspired by Ollie Charles and his excellent [24 days of Hackage](https://ocharles.org.uk/blog/pages/2013-12-01-24-days-of-hackage.html) series, I'm going to try and introduce you to a number of Rust language features, useful libraries and cool projects built with Rust. In fact this is a learning opportunity for me too - as much as I love Rust, I'm just diving in. If you think I'm wrong or know an interesting library you want me to write about, feel free to comment! 4 | 5 | So let's start! As in the Haskell series by Ollie, where he [introduced Cabal](https://ocharles.org.uk/blog/posts/2012-12-01-24-days-of-hackage.html), the first post will briefly cover package management. For those of you coming from Python, Ruby or Node this may be a bit familiar. C++ does not have a dedicated package manager, so hopefully this is one of the selling points of Rust for people with such background. 6 | 7 | Cargo 8 | ----- 9 | 10 | **Cargo** is the package manager for Rust. It comes installed along with the compiler if you use the `rustup.sh` script. Cargo builds your code and manages its dependencies. It can also generate a basic project structure for you, if you're just starting a new one: 11 | 12 | ```sh 13 | $ cargo new myproject --bin # or --lib, if you're creating a library 14 | ``` 15 | 16 | Cargo will then generate some initial sources and a `Cargo.toml` file (sometimes called a *manifest*). This is where you describe your project's metadata, such as name, version, etc. It's also the right place to declare any possible dependencies your project might have. See for example [Cargo.toml](https://github.com/zsiciarz/euler.rs/blob/7e7f93c395a8eb010221015fa3585d8c70663cd7/Cargo.toml) from one of my toy projects: 17 | 18 | ```ini 19 | [package] 20 | 21 | name = "euler" 22 | version = "0.0.1" 23 | authors = ["Zbigniew Siciarz "] 24 | 25 | [dependencies] 26 | getopts = "~0.2.14" 27 | num = "~0.1.36" 28 | permutohedron = "~0.2.2" 29 | primal = "~0.2.3" 30 | ``` 31 | 32 | Cargo can also run a test suite, generate documentation or upload your crate to the repository, but that's the topic for later. 33 | 34 | Crates and dependencies 35 | ----------------------- 36 | 37 | If you don't know, Rust calls its compilation unit (be it a library or an executable) a **crate**. Your Rust program usually lives in one crate, but it can use other crates as dependencies. See the [Rust Guide on crates](http://doc.rust-lang.org/guide.html#crates-and-modules) for details on the difference between crates and modules. 38 | 39 | Executing `cargo build` compiles your crate and the resulting binary can be found inside the `target/` directory. For executables there's a nice shortcut - `cargo run` - that automatically runs your program after it's compiled. 40 | 41 | You probably noticed a `[dependencies]` section in the manifest file above. You guessed it - this is where you declare which external libraries you'll use. But `Cargo.toml` is not enough - it just tells Cargo to link your project with these crates. In order to use their APIs in your code, put `extern crate` imports in your project's crate root (that is usually the `main.rs` or `lib.rs` file), so that the compiler can resolve function names, types etc. 42 | 43 | ```rust 44 | // main.rs 45 | extern crate num; 46 | 47 | fn main () { 48 | // ... use stuff from num library 49 | } 50 | ``` 51 | 52 | **Note**: At the moment Cargo supports only source dependencies; it downloads the source code for every crate your project depends on, compiles them locally on your machine and finally links with your main crate. 53 | 54 | What I personally like about Cargo is that it's very simple to build both a library and an executable using that library. It's just a matter of having two sections in the manifest. For example, this is what [rust-cpuid](https://github.com/zsiciarz/rust-cpuid) does: 55 | 56 | ```ini 57 | [lib] 58 | name = "cpuid" 59 | 60 | [[bin]] 61 | name = "cpuid" 62 | ``` 63 | 64 | Then I just need to put `extern crate cpuid` in my `main.rs` file (this is the crate root for the executable) and Cargo will link it with the library it built alongside. 65 | 66 | **Update**: As [Steve Klabnik](http://www.reddit.com/r/rust/comments/2nybtm/24_days_of_rust_cargo_and_cratesio/cmip7xw) noted, these two sections are redundant as they're the defaults. If you have both `lib.rs` and `main.rs` in the source directory, Cargo will build the library and executable just the same. 67 | 68 | crates.io 69 | --------- 70 | 71 | [crates.io](https://crates.io/) is the central package repository for Cargo. (Python folks - think of PyPI.) Cargo pulls dependencies from there, although it can also install them from Git or local filesystem. Once you've built an awesome library and want to share it with the rest of the world, crates.io is the place to go. You'll need an account on the site (currently GitHub-based social login only, but this may change). The relevant command is `cargo publish`, documented in the [section on publishing crates](http://doc.crates.io/crates-io.html#publishing-crates). Once you've done it, congratulations, you are now a contributor to the Rust ecosystem! 72 | -------------------------------------------------------------------------------- /book/vol1/day10.md: -------------------------------------------------------------------------------- 1 | # Day 10 - the glorious tau 2 | 3 | > Relevancy: 1.9 stable 4 | 5 | τ (*tau*) is one of the most important mathematical constants (if not *the* most important), relating circle's circumference to it's radius. See the [tau manifesto](http://www.tauday.com/tau-manifesto) for the long explanation if you're still an unbeliever. Can we use it in Rust? Of course, there's a [crate for that](https://crates.io/crates/tau)! 6 | 7 | Let's see this amazing number in full glory: 8 | 9 | [include:2-2](../../vol1/src/bin/day10.rs) 10 | [include:8-8](../../vol1/src/bin/day10.rs) 11 | 12 | ```sh 13 | $ cargo run 14 | τ = 6.283185307179586 15 | ``` 16 | 17 | We can do all sorts of important calculations with τ, just look: 18 | 19 | [include:9-13](../../vol1/src/bin/day10.rs) 20 | 21 | And if someone really, I mean *really* needs to refer to that other mathematical constant, it is (regrettably) possible as well. 22 | 23 | [include:14-14](../../vol1/src/bin/day10.rs) 24 | 25 | Here's the output: 26 | 27 | ```sh 28 | $ cargo run 29 | circle circumference = τ * r = 94.24777960769379 30 | Euler's identity: exp(i * τ) = 1-0.00000000000000024492935982947064i 31 | Trigonometry: sin(τ) = -0.00000000000000024492935982947064, cos(τ) = 1 32 | That other constant = 3.141592653589793 33 | ``` 34 | -------------------------------------------------------------------------------- /book/vol1/day11.md: -------------------------------------------------------------------------------- 1 | # Day 11 - postgres 2 | 3 | > Relevancy: 1.9 stable (macros only on nightly) 4 | 5 | Yes, I'm biased. [PostgreSQL](http://www.postgresql.org/) is my favorite SQL database. There is already a pure Rust driver for PostgreSQL - the [postgres](https://crates.io/crates/postgres) crate which will be the subject of today's article. 6 | 7 | Connecting 8 | ---------- 9 | 10 | In this and the following examples we will assume a Postgres user `rust` with password `rust` and an existing database named... well, `rust`. 11 | 12 | [include:6-6](../../vol1/src/bin/day11.rs) 13 | [include:10-10](../../vol1/src/bin/day11.rs) 14 | [include:32-39](../../vol1/src/bin/day11.rs) 15 | 16 | The `Connection` type has a few methods related to making queries; perhaps the simplest one is `execute()` which immediately executes the query and returns the number of modified rows (or an error). This method can be used for example for insert/update queries but also DDL as shown below. 17 | 18 | [include:40-46](../../vol1/src/bin/day11.rs) 19 | 20 | The second argument looks slightly awkward, but that's the way of telling `execute()` that the query takes no parameters. Later on we will see a few examples that use query parameters. 21 | 22 | Prepared queries and statements 23 | ------------------------------- 24 | 25 | Let's add a few rows to our table. We will use the `prepare()` method. 26 | 27 | [include:47-58](../../vol1/src/bin/day11.rs) 28 | 29 | The query was prepared only once and what we got (after some crude error handling) is a `Statement` value. We can use its `execute()` method to actually run the query with the supplied parameters (note the borrowing). 30 | 31 | To read the data from the database we will use the same `prepare()` method in conjunction with the `query()` method of a `Statement`. There's a significant difference between `execute()` and `query()`: the former returns just the number of affected rows, while the latter returns a collection of `Row` values. 32 | 33 | [include:59-72](../../vol1/src/bin/day11.rs) 34 | 35 | Keep in mind that the `get()` method will panic if it encounters incompatible types, for example if we changed `String` to `i32` above. There's also a safer `get_opt()` method returning a `Result` instead of panicking. 36 | 37 | Advanced PostgreSQL types 38 | ------------------------- 39 | 40 | All this is a bit boring so far. One of the reasons developers love PostgreSQL is its selection of interesting data types. Let's see how to use them in Rust (hint: it's kinda cool). We'll start from writing a generic helper function to read a single value from the first column of the first row. 41 | 42 | [include:10-12](../../vol1/src/bin/day11.rs) 43 | [include:20-28](../../vol1/src/bin/day11.rs) 44 | 45 | We use the `try!` macro to minimize the noise from error handling. Now let's see it in action. The more interesting types like arrays, ranges etc. come from a few additional crates: [postgres_array](https://crates.io/crates/postgres_array) and [postgres_range](https://crates.io/crates/postgres_range). 46 | 47 | [include:4-8](../../vol1/src/bin/day11.rs) 48 | [include:13-18](../../vol1/src/bin/day11.rs) 49 | [include:73-93](../../vol1/src/bin/day11.rs) 50 | 51 | ```sh 52 | $ cargo run 53 | Executing query: select 1=1 54 | Ok(true) 55 | Executing query: select 1=1 56 | Err(WrongType(Bool)) 57 | Executing query: select '{4, 5, 6}'::int[] 58 | Ok([4, 5, 6]) 59 | Executing query: select '{"foo": "bar", "answer": 42}'::json 60 | Ok(Object({"answer": U64(42), "foo": String("bar")})) 61 | Executing query: select '[10, 20)'::int4range 62 | Ok([10,20)) 63 | Executing query: select '[2015-01-01, 2015-12-31]'::tsrange 64 | Ok([Timespec { sec: 1420070400, nsec: 0 },Timespec { sec: 1451520000, nsec: 0 }]) 65 | ``` 66 | 67 | Fantastic! The error handling is still there when we need it and we get values of reasonable Rust types. 68 | 69 | Compile-time SQL checking 70 | ------------------------- 71 | 72 | There is another crate worth mentioning here - [postgres_macros](https://crates.io/crates/postgres_macros). It provides the `sql!` macro that validates correctness of the SQL query given as argument **at compile time**. 73 | 74 | ```rust 75 | let query = sql!("select '{4, 5, 6}'::int[]"); 76 | let this_wont_compile = sql!("eslect '{4, 5, 6}'::int[]"); 77 | ``` 78 | 79 | See also 80 | -------- 81 | 82 | * [deuterium-orm](https://github.com/deuterium-orm/deuterium-orm) - a basic ORM for Rust supporting PostgreSQL 83 | * [r2d2](https://crates.io/crates/r2d2) - a generic connection pool with [Postgres support](https://crates.io/crates/r2d2_postgres) 84 | * [Building RESTful API server in Rust with nickel-postgres](http://blog.bguiz.com/2014/08/05/restful-api-in-rust-with-nickel-postgres/) 85 | * [Writing Postgres extensions in Rust](https://github.com/thehydroimpulse/postgres-extension.rs) 86 | -------------------------------------------------------------------------------- /book/vol1/day12.md: -------------------------------------------------------------------------------- 1 | # Day 12 - image 2 | 3 | > Relevancy: 1.9 stable 4 | 5 | The [image](https://crates.io/crates/image) crate is a library under development (well, not unlike the rest of the Rust ecosystem before 1.0) to read, manipulate and write images. It is part of the effort to develop an open source game engine in pure Rust - [Piston](http://www.piston.rs/), but of course the `image` crate can be used on its own. 6 | 7 | At the moment `image` supports reading and writing JPG, GIF and PNG images, while a few other formats are read-only (TIFF, WEBP). 8 | 9 | Basics 10 | ------ 11 | 12 | Let's start from something simple - read a JPEG image, flip it horizontally and save as PNG. 13 | 14 | [include:1-1](../../vol1/src/bin/day12.rs) 15 | [include:7-7](../../vol1/src/bin/day12.rs) 16 | [include:11-14](../../vol1/src/bin/day12.rs) 17 | 18 | We used the `open()` function to create the `img` variable (which is a [DynamicImage](http://www.piston.rs/image/image/enum.DynamicImage.html) value). This is a wrapper for the `load()` function, which can read images from anything that implements the `Read` trait. However, there's no symmetrical shortcut to write an image, so we take advantage of the fact that `File` implements `Write`. In between, the `fliph()` method of the image does what it says on the cover. There are other transformations available, such as: 19 | 20 | * `fliph()` 21 | * `flipv()` 22 | * `rotateN()` where N is 90, 180 or 270 23 | * `blur(sigma)` 24 | * `invert()` 25 | * `grayscale()` 26 | 27 | and a few others. All these return a new image, except `invert()`. 28 | 29 | Edge detection 30 | -------------- 31 | 32 | The `image` API lets us run arbitrary 3x3 [convolution filters](http://www.roborealm.com/help/Convolution.php). We can use it to create a very basic edge detection filter. 33 | 34 | [include:16-19](../../vol1/src/bin/day12.rs) 35 | 36 | To honour the image processing tradition, our input image is [a photo of Lena Söderberg](http://en.wikipedia.org/wiki/Lenna). Here's the result: 37 | 38 | ![edgy Lena](//i.imgur.com/D1mMwhK.jpg) 39 | 40 | Directly manipulating pixels 41 | ---------------------------- 42 | 43 | A typical example of looping over image pixels is to add some noise to the image. The noise does not depend on surrounding pixels, so the inner loop is very simple - generate a Gaussian noise sample and add it to the current pixel. 44 | 45 | [include:23-33](../../vol1/src/bin/day12.rs) 46 | 47 | We need to use the `GenericImage` and `Pixel` traits to introduce some extra methods we're going to use later. 48 | 49 | Each run of the inner loop generates a sample from the normal distribution. We then pick one pixel from the original image with `get_pixel()`, add the offset to every channel (that's what the `map()` method does for types implementing `Pixel` trait) and store the pixel in the new image. 50 | 51 | ![noisy Lena](//i.imgur.com/Zu7jnIK.jpg) 52 | 53 | Thumbnails 54 | ---------- 55 | 56 | To create a thumbnail from the image, use it's `resize()` method. It takes three arguments: width and height of the thumbnail (but the original aspect ratio will be preserved, so one of these dimensions might be ignored) and a variant of the `FilterType` enum. This value dictates what interpolation to use when resizing. See for example the [GIMP documentation](http://docs.gimp.org/en/gimp-tools-transform.html) to learn more about various methods. I personally like Lanczos interpolation, unless the result looks really bad. 57 | 58 | [include:37-37](../../vol1/src/bin/day12.rs) 59 | 60 | One more thing - if your code using the `image` crate seems to be pretty slow, double check that you run in release mode (with compiler optimizations). For Cargo, that means `cargo run --release`. In my case the change from the default (no optimization) to release mode resulted in an 8-10x increase in speed. 61 | -------------------------------------------------------------------------------- /book/vol1/day13.md: -------------------------------------------------------------------------------- 1 | # Day 13 - uuid 2 | 3 | > Relevancy: 1.9 stable 4 | 5 | Writing the [Postgres chapter](day11.md) reminded me of one more cool thing in PostgreSQL - native support for the [UUID](http://www.postgresql.org/docs/9.4/static/datatype-uuid.html) type. The popular web framework Django [will soon get a UUID field](https://github.com/django/django/commit/ed7821231b7dbf34a6c8ca65be3b9bcbda4a0703), Rails [already support it](http://edgeguides.rubyonrails.org/active_record_postgresql.html#uuid). So what's the big deal? 6 | 7 | Simply put, using UUIDs can improve distributed systems by removing the need for a centralized ID generation. Let me quote [Andrzej Krzywda's justification](http://andrzejonsoftware.blogspot.com/2013/12/decentralise-id-generation.html): 8 | 9 | > For years, I have been thinking that centralised id generation is the natural way. Seems obvious. After I learnt mote about UUID, I start seeing more and more good things about it. It allows for less coupling between components/services. You no longer need to 'ask' for the id. In case of JavaScript frontends it solves the problem of whether to block the UI, when we create a new resource. 10 | 11 | For me, looking from the web development perspective, this is an area of experimentation as I'm not using UUIDs in any of my public projects (yet). However most of the languages I use have a function or library to generate UUIDs and Rust is no exception here. The [uuid](https://crates.io/crates/uuid) crate was once in the standard library, but [got pulled out](https://github.com/rust-lang/rust/issues/8784) during the cleanup. 12 | 13 | As usual we'll start by adding the dependency to `Cargo.toml`: 14 | 15 | ```ini 16 | [dependencies.uuid] 17 | version = "~0.2.0" 18 | features = ["v4"] 19 | ``` 20 | 21 | Let's generate a few UUIDs and display them in the most familar form: 22 | 23 | [include:1-3](../../vol1/src/bin/day13.rs) 24 | [include:7-9](../../vol1/src/bin/day13.rs) 25 | 26 | The example output would look like: 27 | 28 | ```sh 29 | $ cargo run 30 | 982bb2a5-9e32-450a-9736-bb2dd00d0b4a 31 | 9b44c913-c28f-4f5a-be56-2aec4aaa5384 32 | 86f137e7-2d8e-4224-9f7a-1724a9503e41 33 | 59db4c26-46f7-49b9-9942-636bdc0c1f89 34 | 49326e6f-f9c3-40b1-8041-f7dadd503c81 35 | 0eabbd4d-13e8-4a76-9892-119096cdab72 36 | 6f530ac3-4605-459a-9df7-73026f15c361 37 | 17fccb8e-f5e2-4281-ba75-82a50d7f007e 38 | 3c7c02b2-4a50-4a56-8a21-bcde9d46aff0 39 | 5a405806-f967-4228-a20a-2afaec18e501 40 | ``` 41 | 42 | We can use the `parse_str()` method to check if a string represents a valid UUID and convert it to an `Uuid` value. 43 | 44 | [include:10-13](../../vol1/src/bin/day13.rs) 45 | 46 | (Web developers might recognize that specific value.) Here's the output: 47 | 48 | ```sh 49 | $ cargo run 50 | Err(InvalidLength(35)) 51 | Err(InvalidCharacter('x', 0)) 52 | Err(InvalidGroupLength(0, 7, 8)) 53 | Ok(Uuid { bytes: [210, 124, 219, 110, 174, 109, 17, 207, 150, 184, 68, 69, 83, 84, 0, 0] }) 54 | ``` 55 | 56 | See also 57 | -------- 58 | 59 | * [Use UUIDs as Keys](http://blog.joevandyk.com/2013/08/14/uuids-as-keys/) 60 | * [A Better ID Generator for PostgreSQL](http://rob.conery.io/2014/05/29/a-better-id-generator-for-postgresql/) 61 | * [How to start using UUID in ActiveRecord with PostgreSQL](http://blog.arkency.com/2014/10/how-to-start-using-uuid-in-activerecord-with-postgresql/) 62 | * The [postgres](https://crates.io/crates/uuid) crate supports UUID fields 63 | -------------------------------------------------------------------------------- /book/vol1/day14.md: -------------------------------------------------------------------------------- 1 | # Day 14 - nalgebra 2 | 3 | > Relevancy: 1.9 stable 4 | 5 | The [nalgebra](https://crates.io/crates/nalgebra) crate provides a wide set of mathematical primitives for linear algebra, computer physics, graphic engines etc. I'm not going to dive deep into the underlying math, there are a lot of tutorials and courses, some of them specifically targeted at the programmers. My goal for today is just a brief showcase of what we can do in Rust with `nalgebra`. 6 | 7 | Basic vector and matrix operations 8 | ---------------------------------- 9 | 10 | [include:29-34](../../vol1/src/bin/day14.rs) 11 | 12 | ```sh 13 | $ cargo run 14 | Vec2 { x: 1, y: 0 } 15 | ``` 16 | 17 | In `nalgebra` there are several statically sized vector and square matrix types (for dimensions up to 6). The standard mathematical operators are overloaded, so all allowed kinds of vector/matrix multiplication should just work. In the example above we defined the [rotation matrix](http://en.wikipedia.org/wiki/Rotation_matrix) ourselves, but there is a nice shortcut: the `RotN` type. 18 | 19 | [include:35-37](../../vol1/src/bin/day14.rs) 20 | 21 | The output is the same but this time we tell Rust *what* to do, not *how* to do it. Note that we need to wrap the `angle` in a single-element vector. 22 | 23 | We can use vectors to translate (move) points. 24 | 25 | [include:38-39](../../vol1/src/bin/day14.rs) 26 | 27 | A number of other operations are also exposed as top-level functions, such as `transform()`, `rotate()` along with their inverse counterparts. 28 | 29 | Dot and cross product 30 | --------------------- 31 | 32 | [include:41-48](../../vol1/src/bin/day14.rs) 33 | 34 | The output is: 35 | 36 | ```sh 37 | $ cargo run 38 | v1 is orthogonal to v2 39 | Vec3 { x: 0, y: 0, z: -8 } 40 | Vec3 { x: 0, y: 0, z: 8 } 41 | ``` 42 | 43 | [Dot product](http://en.wikipedia.org/wiki/Dot_product) can be used to check if two vectors are orthogonal to each other. That happens if their dot product is equal to 0. As floating point comparisons are [sometimes suprising](http://www.parashift.com/c++-faq/floating-point-arith.html), we should use the `approx_eq()` function. 44 | 45 | The [cross product](http://en.wikipedia.org/wiki/Cross_product) of two vectors is always perpendicular (sometimes we say **normal**) to *both* of them. As you can see from the example it is also not commutative. Normal vectors are very important in computer graphics for [calculating light and shading](http://www.opengl-tutorial.org/beginners-tutorials/tutorial-8-basic-shading/) of the scene. 46 | 47 | Dynamic vectors 48 | --------------- 49 | 50 | All of the `nalgebra` types we've seen so far have their higher-dimensional variants up to `Vec6`/`Mat6` etc. But what if we want to go further? Very high number of dimensions is common for example in digital signal processing. In `nalgebra` there is a `DVec` type for that purpose. 51 | 52 | [include:50-62](../../vol1/src/bin/day14.rs) 53 | 54 | We can use the `from_fn()` mwthod to create a vector by generating each element in a closure. The `window` variable is a [Hamming window](http://en.wikipedia.org/wiki/Window_function#Hamming_window); such window functions are a common preprocessing step in DSP. 55 | 56 | The `draw()` function borrows a `DVec` and a `Path` and plots a simple representation of the vector to a PNG file using the [image crate](https://siciarz.net/24-days-of-rust-image/). The code is [on GitHub](https://github.com/zsiciarz/24daysofrust/blob/9a88969f156581da8ba16ae50c87d884983b17c9/src/day14.rs#L11) for anybody interested. Here's the output of all three steps of the above code: 57 | 58 | ![sine, window, windowed sine](//i.imgur.com/mQKFms3.png) 59 | 60 | See also 61 | -------- 62 | 63 | * [A First Course in Linear Algebra](http://linear.ups.edu/html/fcla.html) 64 | * [Linear algebra for game developers](http://blog.wolfire.com/2009/07/linear-algebra-for-game-developers-part-1/) 65 | * [kiss3d](https://github.com/sebcrozet/kiss3d) - a simple 3D graphics engine for Rust 66 | -------------------------------------------------------------------------------- /book/vol1/day17.md: -------------------------------------------------------------------------------- 1 | # Day 17 - from_fn 2 | 3 | > Relevancy: 1.9 stable 4 | 5 | In Rust there is no concept of a constructor as a language feature, like for example in C++. However there is a strong convention (and mentioned in [the guidelines](http://aturon.github.io/ownership/constructors.html)) to use a static method called `new` as the constructor. This works well, but you can have only one function called `new` in the `impl` - there is no method overloading. So are we out of luck if we want to have different constructors? No! But arguably different purposes should imply different method names, so another convention is to prefix extra constructors with `with_` (such as [Vec::with_capacity](http://doc.rust-lang.org/std/vec/struct.Vec.html#method.with_capacity)) or `from_`, if the constructor does some kind of a conversion. 6 | 7 | A few types in the standard library and some third-party crates provide a `from_fn` constructor. This curious method usually takes as arguments some sort of *dimensions* and a closure that will generate values. 8 | 9 | Matrices 10 | -------- 11 | 12 | In the [nalgebra crate](https://siciarz.net/24-days-of-rust-nalgebra/) there is a `DMat` type representing a matrix which dimensions are known at runtime. We can build a matrix using the `from_fn` constructor too. Let's create a triangular matrix: 13 | 14 | [include:2-2](../../vol1/src/bin/day17.rs) 15 | [include:6-6](../../vol1/src/bin/day17.rs) 16 | [include:17-18](../../vol1/src/bin/day17.rs) 17 | 18 | The first two arguments to `from_fn` are numbers of rows and columns; this means the closure must take two arguments - indices of the current row and column. And here's our matrix: 19 | 20 | ```sh 21 | $ cargo run 22 | 1 0 0 0 0 0 0 23 | 1 1 0 0 0 0 0 24 | 1 1 1 0 0 0 0 25 | 1 1 1 1 0 0 0 26 | 1 1 1 1 1 0 0 27 | 1 1 1 1 1 1 0 28 | 1 1 1 1 1 1 1 29 | ``` 30 | 31 | Images 32 | ------ 33 | 34 | Bur we're not limited to mathematical objects. For example the [image crate](day12.md) provides a buffer that can generate the image with the `from_fn` call. 35 | 36 | [include:1-1](../../vol1/src/bin/day17.rs) 37 | [include:5-5](../../vol1/src/bin/day17.rs) 38 | [include:19-24](../../vol1/src/bin/day17.rs) 39 | 40 | We're working in two dimensions in the same manner as with the `DMat` type. And here's the generated image: 41 | 42 | ![pattern](//i.imgur.com/G3JuGR0.png) 43 | -------------------------------------------------------------------------------- /book/vol1/day18.md: -------------------------------------------------------------------------------- 1 | # Day 18 - redis 2 | 3 | > Relevancy: 1.9 stable 4 | 5 | Today I'm revisiting the topic of database access in Rust. I mentioned PostgreSQL client library [a week ago](https://siciarz.net/24-days-of-rust-postgres/). This time we'll move from SQL to NoSQL land. Our focus for today will be [Redis](http://redis.io/) - a data structure server. The [redis crate](https://crates.io/crates/redis) is a client library to access Redis from Rust. 6 | 7 | Connecting 8 | ---------- 9 | 10 | The `redis` crate provides a `Client` type that is used to connect to the Redis server. The `get_connection()` method returns a `Connection` object which execute Redis commands. 11 | 12 | [include:2-2](../../vol1/src/bin/day18.rs) 13 | [include:4-4](../../vol1/src/bin/day18.rs) 14 | [include:44-48](../../vol1/src/bin/day18.rs) 15 | 16 | Most of the [Redis commands](http://redis.io/commands) translate directly to `Connection` methods. But if you encounter an error similar to `Type ``redis::connection::Connection`` does not implement any method in scope named ``set`` `, you probably forgot to import the `Commands` trait. 17 | 18 | (I accidentally used the same example number as Armin did in the readme; not surprising since it is the [Answer to the Ultimate Question of Life, the Universe, and Everything](http://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy#Answer_to_the_Ultimate_Question_of_Life.2C_the_Universe.2C_and_Everything_.2842.29).) 19 | 20 | Friends in common 21 | ----------------- 22 | 23 | When viewing someone's profile page on most of the social networking sites, you can see the number (or even a full list) of friends that you both have in common. This is very easy to achieve in Redis using sets. 24 | 25 | In case someone accepts your friendship request, a function similar to the one below will be called. 26 | 27 | [include:5-5](../../vol1/src/bin/day18.rs) 28 | [include:7-15](../../vol1/src/bin/day18.rs) 29 | 30 | I'm assuming here that the friendship relation is mutual. That's why there are two `sadd` calls - one to add yourself to their set of friends and the other one is symmetrical. Now checking friends in common is just a matter of set intersection - expressed in Redis as the `SINTER` command. 31 | 32 | [include:17-21](../../vol1/src/bin/day18.rs) 33 | 34 | We can now simulate adding a few friends: 35 | 36 | [include:50-54](../../vol1/src/bin/day18.rs) 37 | 38 | Here's the output: 39 | 40 | ```sh 41 | $ cargo run 42 | You have 1 friend(s) in common. 43 | ``` 44 | 45 | Leaderboards 46 | ------------ 47 | 48 | [Sorted sets](http://redis.io/commands#sorted_set) are possibly my favorite Redis data structure. They're a perfect fit to create leaderboards for example in online games. Add scores with `ZADD`, fetch the leaderboard with `ZREVRANGE` - that's the gist of it. 49 | 50 | [include:23-25](../../vol1/src/bin/day18.rs) 51 | 52 | The `add_score` function is just a wrapper to provide a more high-level API. It will be called every time player's score changes. 53 | 54 | [include:27-40](../../vol1/src/bin/day18.rs) 55 | 56 | The `Leaderboard` alias is there just to simplify the result type. We use `zrevrange_withscores` to get the leaderboard data (sorted by score descending) and display it using Rust's [string formatting](http://doc.rust-lang.org/std/fmt/) syntax. 57 | 58 | Putting all this together: 59 | 60 | [include:56-61](../../vol1/src/bin/day18.rs) 61 | 62 | And if we run this, we'll get something similar to the output below: 63 | 64 | ```sh 65 | $ cargo run 66 | ----==== Top 3 players ====---- 67 | 1 mengsk 986 68 | 2 tassadar 879 69 | 3 kerrigan 489 70 | ``` 71 | 72 | See also 73 | -------- 74 | 75 | * [User friendships](http://jimneath.org/2011/03/24/using-redis-with-ruby-on-rails.html#example_uses_in_rails) with Redis and Ruby on Rails 76 | * [The Top 3 Game Changing Redis Use Cases](https://redislabs.com/blog/the-top-3-game-changing-redis-use-cases) 77 | * [Creating high score tables (leaderboards) using Redis](http://www.agoragames.com/blog/2011/01/01/creating-high-score-tables-leaderboards-using-redis/) 78 | -------------------------------------------------------------------------------- /book/vol1/day19.md: -------------------------------------------------------------------------------- 1 | # Day 19 - rusti 2 | 3 | > Relevancy: 1.8 nightly 4 | 5 | A few days ago the [Rust subreddit](http://www.reddit.com/r/rust/comments/2phjon/rusti_reborn_my_unofficial_workinprogress_rust/) got excited about the rebirth of [rusti](https://github.com/murarth/rusti) - an interactive shell for Rust. Such interpreters (sometimes called REPLs - [read-eval-print loop](http://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)) are the bread and butter of dynamic languages like Lisp, Python or Ruby. But that is not a requirement - as Haskell demonstrates with its GHCi, you can have a REPL for a statically typed, compiled language too. `rusti` is an attempt to build such an interpreter for Rust. 6 | 7 | `rusti` is still under development and currently installable only from source checkout. Clone the [repository](https://github.com/murarth/rusti) and start the interpreter with `cargo run`: 8 | 9 | ```sh 10 | $ cargo run 11 | Running `target/rusti` 12 | rusti=> 1 + 4 13 | 5 14 | ``` 15 | 16 | `rusti` can evaluate most of Rust code and display results. You can use crates and modules from the standard library as shown in a few examples below. 17 | 18 | ```sh 19 | rusti=> use std::env; 20 | rusti=> env::args().collect::>() 21 | ["target/debug/rusti"] 22 | rusti=> use std::iter::AdditiveIterator; 23 | rusti=> (1..100).filter(|x| *x % 19 == 3).fold(0, |acc, x| acc + x) 24 | 303 25 | ``` 26 | 27 | One great thing about `rusti` is the `.type` command which shows the type of an expression. (It's quite similar to `:type` in GHCi.) 28 | 29 | ```sh 30 | rusti=> .type Some("Hello world!".to_string()) 31 | Some("Hello world!".to_string()) = core::option::Option 32 | rusti=> .type vec![1, 2, 3] 33 | vec![1, 2, 3] = collections::vec::Vec 34 | rusti=> .type std::io::stdin 35 | std::io::stdin = fn() -> std::io::stdio::Stdin {std::io::stdio::stdin} 36 | ``` 37 | 38 | Unfortunately there is no code completion yet, but at least `readline` capabilities (such as line editing and history) are available. 39 | 40 | There are a few other limitations, some of which are rather inconvenient at the moment (such as `let` declarations being *very* local), but the authors are aware of them. The real issue for me today is that it can't reference external crates. However [this is already being discussed](https://github.com/murarth/rusti/issues/2) and I hope it will happen. 41 | -------------------------------------------------------------------------------- /book/vol1/day2.md: -------------------------------------------------------------------------------- 1 | # Day 2 - primal 2 | 3 | > Relevancy: 1.9 stable 4 | 5 | When I start learning a new programming language, I like to code at least several solutions to [Project Euler](https://projecteuler.net/) problems. These are very math-oriented and may not be the best introduction to general purpose programming, but it's a start. Anyway, it's just fun to solve them! (...and way more fun to solve them in a fast way and not by brute force.) 6 | 7 | A lot of Project Euler problems involve prime numbers in some way. These include finding `n`th prime, efficient factorization or checking whether some curious number is prime or not. You could of course write these mathematical procedures yourself, which is also an educational activity. But I'm lazy. I set out to find some ready-made code and stumbled upon the [primal](https://github.com/huonw/primal) library by [Huon Wilson](http://huonw.github.io/). Incidentally this was the first external dependency I ever used in a Rust program, long before crates.io (back when it was called `slow_primes`). 8 | 9 | So let's see what's in there, shall we? 10 | 11 | Prime sieve 12 | ----------- 13 | 14 | The first thing to do is to create a **sieve** (see [Wikipedia on Sieve of Eratosthenes](http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) for a detailed explanation of the algorithm). We need to set an upper bound on the sieve. There's a clever way to estimate that bound (see the docs for [estimate_nth_prime](http://huonw.github.io/primal/primal/fn.estimate_nth_prime.html)) but for simplicity I'll hardcode it now to 10000. 15 | 16 | Let's actually check some numbers for primality: 17 | 18 | [include:1-4](../../vol1/src/bin/day2.rs) 19 | [include:11-16](../../vol1/src/bin/day2.rs) 20 | 21 | How about finding 1000th prime number? 22 | 23 | [include:18-22](../../vol1/src/bin/day2.rs) 24 | 25 | The `primes_from()` method returns an iterator over all prime numbers generated by this sieve (2, 3, 5, 7...). Iterators in Rust have a lot of useful methods; the `nth()` method skips over `n` initial iterations, returning the `n`th element (or `None` if we exhausted the iterator). The argument is zero-based, so to find 1000th prime we need to pass 999 to `nth()`. 26 | 27 | Factorization 28 | ------------- 29 | 30 | Factorization is a way to decompose a number into its divisors. For example, `2610 = 2 * 3 * 3 * 5 * 29`. Here's how we can find it out with `primal` API: 31 | 32 | [include:23-23](../../vol1/src/bin/day2.rs) 33 | 34 | When we run this, we'll get: 35 | 36 | ```sh 37 | $ cargo run 38 | Ok([(2, 1), (3, 2), (5, 1), (29, 1)]) 39 | ``` 40 | 41 | What is this? Let's have a look at the result type of `factor()`: 42 | 43 | ```rust 44 | type Factors = Vec<(usize, usize)>; 45 | fn factor(&self, n: usize) -> Result 46 | ``` 47 | 48 | Looks a bit complicated, but remember the [Result](http://doc.rust-lang.org/std/result/enum.Result.html) type. The `Ok` variant wraps a vector of pairs of numbers. Each pair contains a prime factor and its exponent (how many times it appears in the factorization). In case of an error we'll get a pair (leftover value, partial factorization). 49 | 50 | We can use factorization to find the total number of divisors (including compound ones). This is very important in number theory (although for reasons that are outside the scope of this blog). 51 | 52 | Consider the following function: 53 | 54 | [include:5-9](../../vol1/src/bin/day2.rs) 55 | 56 | The trick is to multiply all prime factor exponents, incremented before multiplication. See the [explanation at Maths Challenge](http://mathschallenge.net/library/number/number_of_divisors) for the curious. So when we call the function on our 2610 example, we'll get `Some(24)` as a result. 57 | 58 | [include:24-24](../../vol1/src/bin/day2.rs) 59 | 60 | Further reading 61 | --------------- 62 | 63 | * [Divisor function](http://en.wikipedia.org/wiki/Divisor_function) 64 | * [Miller-Rabin primality test](http://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test) 65 | * [GMP algorithms page](https://gmplib.org/manual/Algorithms.html#Algorithms) 66 | * [consecutive prime sum](https://projecteuler.net/problem=50) - an interesting Project Euler problem 67 | -------------------------------------------------------------------------------- /book/vol1/day20.md: -------------------------------------------------------------------------------- 1 | # Day 20 - zeromq 2 | 3 | > Relevancy: 1.9 stable 4 | 5 | [ZeroMQ](http://zeromq.org/) is a language-independent messaging solution. It's not a full-fledged system such as for example [RabbitMQ](http://www.rabbitmq.com/), basically it's just a transport layer. From the programmer's perspective working with it doesn't differ much from ordinary sockets, but there's a lot of power hidden underneath. The [rust-zmq](https://github.com/erickt/rust-zmq) crate is a Rust binding to the C library. There used to be a working native binding ([zeromq](https://github.com/zeromq/zmq.rs)), but it's now undergoing a redesign and rewrite. 6 | 7 | Operational patterns 8 | -------------------- 9 | 10 | The [ZeroMQ guide](http://zguide.zeromq.org/page:all#Messaging-Patterns) lists several messaging patterns such as request-response, pub-sub, pipeline etc. Different patterns suit different needs for distributed systems; for example the request-response pattern is commonly used for remote procedure calls. This is the only mode of operation implemented in the `zeromq` crate at the moment, but hopefully more will be added soon. 11 | 12 | Before we start implementing the client and server, let's prepare some boilerplate code. We will decide whether to run our demo program as client or server based on the commandline argument. 13 | 14 | [include:2-2](../../vol1/src/bin/day20.rs) 15 | [include:5-5](../../vol1/src/bin/day20.rs) 16 | [include:44-58](../../vol1/src/bin/day20.rs) 17 | 18 | Client 19 | ------ 20 | 21 | [include:9-20](../../vol1/src/bin/day20.rs) 22 | 23 | A ZeroMQ request starts with opening a `REQ` socket. The sockets send and receive `Message` objects. You can use any encoding you like, ZeroMQ doesn't enforce anything. It can be JSON, [msgpack](https://github.com/mneumann/rust-msgpack), [protobuf](https://github.com/stepancheg/rust-protobuf), whatever - as long as you push some bytes over the wire, ZeroMQ is happy. 24 | 25 | Note that we're using the [try! macro](http://doc.rust-lang.org/std/result/#the-try!-macro) for error handling. 26 | 27 | Server 28 | ------ 29 | 30 | We're going to build a simple echo server that repeats the incoming message in the response. 31 | 32 | [include:23-33](../../vol1/src/bin/day20.rs) 33 | 34 | The server opens a `REP` socket and then loops infinitely and echoes back incoming message data. In my first implementation I forgot to send the response and got weird socket errors - turns out a response is necessary in a request/response mode, who would have thought... 35 | 36 | Let's start the server now: 37 | 38 | ```sh 39 | $ cargo run -- server 40 | ZeroMQ server listening on tcp://127.0.0.1:25933 41 | ``` 42 | 43 | And if we fire up the client in a new tab we should see a roundtrip message: 44 | 45 | ```sh 46 | $ cargo run -- client 47 | ZeroMQ client connecting to tcp://127.0.0.1:25933 48 | -> "Hello world!" 49 | <- "Hello world!" 50 | ``` 51 | 52 | See also 53 | -------- 54 | 55 | * [ZeroMQ an introduction](http://nichol.as/zeromq-an-introduction) 56 | * [rust-zmq](https://github.com/erickt/rust-zmq) - Rust bindings to the C ZeroMQ library 57 | * [ØMQ Messaging Patterns](http://learning-0mq-with-pyzmq.readthedocs.org/en/latest/pyzmq/patterns/patterns.html) 58 | -------------------------------------------------------------------------------- /book/vol1/day21.md: -------------------------------------------------------------------------------- 1 | # Day 21 - rust-crypto 2 | 3 | > Relevancy: 1.9 stable 4 | 5 | The [rust-crypto](https://crates.io/crates/rust-crypto) crate is a collection of a lot of cryptography primitives and algorithms. There are tools for calculating hashes, verifying data integrity, encryption etc. One disclaimer - it hasn't had a proper security audit yet and although the algorithms are well known and researched, the library itself *might* have security bugs. But [which](http://heartbleed.com/) [one](https://www.mozilla.org/en-US/security/advisories/mfsa2014-73/) [doesn't](http://www.gnutls.org/security.html)? 6 | 7 | Cryptographic hashes 8 | -------------------- 9 | 10 | Let's start with a simple task of computing a [cryptographic hash](http://en.wikipedia.org/wiki/Cryptographic_hash_function) of some value. We'll use the SHA-256 algorithm to demonstrate. 11 | 12 | [include:1-1](../../vol1/src/bin/day21.rs) 13 | [include:6-6](../../vol1/src/bin/day21.rs) 14 | [include:9-9](../../vol1/src/bin/day21.rs) 15 | [include:20-23](../../vol1/src/bin/day21.rs) 16 | 17 | All hash algorithms in rust-crypto implement the `Digest` trait, which defines the low level methods such as `input()` and `result()` operating on bytes, but also convenience string-based methods as shown above. We can use those low level methods for example to represent our hash as a base64-encoded string: 18 | 19 | [include:3-3](../../vol1/src/bin/day21.rs) 20 | [include:10-10](../../vol1/src/bin/day21.rs) 21 | [include:15-15](../../vol1/src/bin/day21.rs) 22 | [include:24-26](../../vol1/src/bin/day21.rs) 23 | 24 | Here's the output: 25 | 26 | ```sh 27 | $ cargo run 28 | c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a 29 | wFNeS+K3n/2TKRMFQ2v4iTFOSj+uwF7P/Lt98xrZ5Ro= 30 | ``` 31 | 32 | Ciphers 33 | ------- 34 | 35 | To actually encrypt some data in a way that we can decrypt it back later, we need a **cipher**. A few of these are provided by the `rust-crypto` crate, here's an example of [AES](http://en.wikipedia.org/wiki/Advanced_Encryption_Standard) encryption in CTR mode: 36 | 37 | [include:5-5](../../vol1/src/bin/day21.rs) 38 | [include:10-10](../../vol1/src/bin/day21.rs) 39 | [include:16-16](../../vol1/src/bin/day21.rs) 40 | [include:28-39](../../vol1/src/bin/day21.rs) 41 | 42 | We generate the secret key and a nonce value with a secure random generator - `OsRng`. Then we need to call the `ctr()` function which returns a *best possible* implementation of AES-CTR cipher (taking into consideration CPU architecture etc.). What we get back is a trait object - a `SynchronousStreamCipher` value in a `Box`. Calling `process()` should encrypt our secret message and store the result bytes in the `output` vector. 43 | 44 | Here's the output: 45 | 46 | ```sh 47 | $ cargo run 48 | Key: NvDy+u51EfMC+amJzoJO+w== 49 | Nonce: d5+SLyfPGUSeug50nK1WGA== 50 | Ciphertext: vTVxjyUms4Z4jex/OcMcQlY= 51 | ``` 52 | 53 | We can decrypt this with Python (who said it would be Rust all the way?): 54 | 55 | ```python 56 | import base64 57 | from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes 58 | from cryptography.hazmat.backends import default_backend 59 | 60 | key = base64.decodebytes(b'NvDy+u51EfMC+amJzoJO+w==') 61 | nonce = base64.decodebytes(b'd5+SLyfPGUSeug50nK1WGA==') 62 | ct = base64.decodebytes(b'vTVxjyUms4Z4jex/OcMcQlY=') 63 | backend = default_backend() 64 | cipher = Cipher(algorithms.AES(key), modes.CTR(nonce), backend=backend) 65 | decryptor = cipher.decryptor() 66 | print(decryptor.update(ct) + decryptor.finalize()) 67 | ``` 68 | 69 | ```sh 70 | $ python3 decrypt.py 71 | b'I like Nickelback' 72 | ``` 73 | 74 | HMAC 75 | ---- 76 | 77 | Message authentication algorithms such as [HMAC](http://en.wikipedia.org/wiki/Hash-based_message_authentication_code) verify **both** data integrity and authenticity. Let's see how to generate a MAC for a given message: 78 | 79 | [include:7-8](../../vol1/src/bin/day21.rs) 80 | [include:13-13](../../vol1/src/bin/day21.rs) 81 | [include:41-48](../../vol1/src/bin/day21.rs) 82 | 83 | As with the AES cipher example, we generate a random secret key. Then we create the `Hmac` value, passing a cryptographic hash function object (anything that implements `Digest`). Since `Hmac` doesn't have these convenience methods for working with strings, we manually feed the bytes and encode the digest to hexadecimal string. The program outputs a key (we should give it to the recipient of the message through some other secure channel) and an HMAC digest. 84 | 85 | ```sh 86 | $ cargo run 87 | Message: Ceterum censeo Carthaginem esse delendam 88 | HMAC key: esU5jdGCbM7E/ME5WBECJ+BdX3kt7bcQ3HkeEK+W6ZQ= 89 | HMAC digest: b3240371a17e1e9755b89b23449f0d85c4c361e94e081c7adbe5a89c2d901aaa 90 | ``` 91 | 92 | And here's a simple Python program to verify validity of the message using the [hmac module](https://docs.python.org/3.4/library/hmac.html): 93 | 94 | ```python 95 | import base64 96 | import hashlib 97 | import hmac 98 | 99 | key = base64.decodebytes(b'esU5jdGCbM7E/ME5WBECJ+BdX3kt7bcQ3HkeEK+W6ZQ=') 100 | message = b'Ceterum censeo Carthaginem esse delendam' 101 | expected = 'b3240371a17e1e9755b89b23449f0d85c4c361e94e081c7adbe5a89c2d901aaa' 102 | h = hmac.new(key, message, hashlib.sha256) 103 | print(hmac.compare_digest(h.hexdigest(), expected)) 104 | ``` 105 | 106 | So... is the message valid and authentic? 107 | 108 | ```sh 109 | $ python3 verify.py 110 | True 111 | ``` 112 | 113 | See also 114 | -------- 115 | 116 | * [Thoughts on Rust cryptography](https://speakerdeck.com/tarcieri/thoughts-on-rust-cryptography) 117 | * [Cryptography 1](https://www.coursera.org/course/crypto) - an introductory cryptography course - recommended! 118 | * [extensive comment](https://github.com/DaGenix/rust-crypto/blob/340cc5f142601077d6838eb6aa0c3b29b7f67358/src/rust-crypto/aessafe.rs#L9) in rust-crypto about AES timing issues 119 | * [Should we MAC-then-encrypt or encrypt-then-MAC?](http://crypto.stackexchange.com/questions/202/should-we-mac-then-encrypt-or-encrypt-then-mac) (short answer: **encrypt-then-MAC**) 120 | -------------------------------------------------------------------------------- /book/vol1/day22.md: -------------------------------------------------------------------------------- 1 | # Day 22 - built with Rust 2 | 3 | Today I'm not going to focus on any specific tool or library. Instead this blogpost will be a showcase of cool things built with Rust. Even though the language is still before 1.0 (but [we're getting there](http://blog.rust-lang.org/2014/12/12/1.0-Timeline.html)!), there are already a few interesting projects out in the wild. 4 | 5 | And I don't mean only development libraries - I've been writing about them for the last three weeks :-) 6 | 7 | Rust 8 | ---- 9 | 10 | This is kinda obvious. Well, maybe not... First versions of the Rust compiler were written in OCaml. Since then every `rustc` release is built with the previous version, and so on. Such an approach is sometimes called [dogfooding](http://en.wikipedia.org/wiki/Eating_your_own_dog_food). 11 | 12 | Servo 13 | ----- 14 | 15 | The compiler and libraries are not the only rusty things coming from Mozilla. [Servo](https://github.com/servo/servo/) is an experimental web browser engine pursuing the same values as Rust does: safe, concurrent and fast. It's still too early to integrate it in a browser, but it [passes ACID2](http://i.imgur.com/CsLkgLl.png) and is improving day by day. 16 | 17 | For a much simpler browser rendering engine in Rust check out [robinson](https://github.com/mbrubeck/robinson) by Matt Brubeck, as well as his awesome blogpost series - [Let's build a browser engine!](http://limpet.net/mbrubeck/2014/08/08/toy-layout-engine-1.html). 18 | 19 | iota 20 | ---- 21 | 22 | [iota](https://github.com/gchp/iota) is a work-in-progress console text editor. At the moment it's capabilities are really basic and I don't like emacs-based keybindings, but I've already written one of the examples for 24 days of Rust in iota, just to try it out. 23 | 24 | wtftw 25 | ----- 26 | 27 | [wtftw](https://github.com/Kintaro/wtftw) is a tiling window manager similar to [xmonad](http://xmonad.org/). I must confess I don't use a tiling WM (yet) so I can't state my personal opinion, but the [community reaction](http://www.reddit.com/r/rust/comments/2pkx94/wtftw_released_feedback_welcome/) was very positive. The author [started a tutorial](https://kintaro.github.io/rust/window-manager-in-rust-01/) on how to write a window manager. I'm very much looking forward to it. 28 | 29 | coreutils 30 | --------- 31 | 32 | This [coreutils](https://github.com/uutils/coreutils) repository is an attempt to rewrite GNU coreutils in Rust. I find it interesting to dive into the sources of some program, for example [wc](https://github.com/uutils/coreutils/blob/9a281adc1e4db8da60b2aac0c41ba0e789be8f97/src/wc/wc.rs), and read the code. This is sometimes inspiring, sometimes... not so much. But the big win for Rust coreutils is that it should work on Windows, at least these commands that make sense there. 33 | 34 | hematite 35 | -------- 36 | 37 | [hematite](https://github.com/PistonDevelopers/hematite) is a simplistic Minecraft clone in Rust using the [Piston](http://www.piston.rs/) game engine. Even though I know next to nothing about writing 3D games, Piston with SDL2 looks interesting. 38 | 39 | More? 40 | ----- 41 | 42 | I've picked only a handful of Rust projects that caught my attention in the last few months. You can go to [rustkit.io](http://rustkit.io/) or [crates.io](https://crates.io/) to see new and trending libraries or browse the [Rust subreddit](http://www.reddit.com/r/rust) for new project announcements. 43 | -------------------------------------------------------------------------------- /book/vol1/day24.md: -------------------------------------------------------------------------------- 1 | # Day 24 - conclusion 2 | 3 | So, 24 days have passed since [the first article](day1.md) in the series. This means, sadly, 24 days of Rust is coming to an end. I hope you learned something from my articles or at least found something interesting about Rust to dive in. For me it was a great experience too, I learned a ton while doing this. Sometimes it was stressful too - breaking changes coming to `rustc` on a daily basis meant a considerable amount of work! A few times I had an already drafted article on some subject, only to discover the code blows up with the newest nightly and I have to change the topic. So here I go looking for something that works on that day, or fixing dependencies of dependencies just to make the examples compile. But no more complaining - there's something very positive I wanted to say today. 4 | 5 | Thanks! 6 | ------- 7 | 8 | And that positive thing is a huge **THANK YOU** to everybody who read my articles, commented or critiqued. Your feedback is invaluable and it helped me get motivated to write more. Doesn't matter if it was in person or on Twitter, Reddit, Hacker News - thanks, you're awesome! Rusty Holidays to all of you! 9 | 10 | 24 days of Rust 2014 - archive 11 | ------------------------------ 12 | 13 | See the sidebar for a complete set of links for the series. 14 | 15 | For a historical perspective you can take a look at the articles as they were originally written on my blog (note: these are pretty much **outdated** as of now): 16 | 17 | * [Day 1 - Cargo and crates.io](https://siciarz.net/24-days-rust-cargo-and-cratesio/) 18 | * [Day 2 - slow_primes](https://siciarz.net/24-days-rust-slow_primes/) 19 | * [Day 3 - csv](https://siciarz.net/24-days-of-rust-csv/) 20 | * [Day 4 - docopt](https://siciarz.net/24-days-of-rust-docopt/) 21 | * [Day 5 - hyper](https://siciarz.net/24-days-of-rust-hyper/) 22 | * [Day 6 - working with JSON](https://siciarz.net/24-days-of-rust-working-json/) 23 | * [Day 7 - itertools](https://siciarz.net/24-days-of-rust-itertools/) 24 | * [Day 8 - racer](https://siciarz.net/24-days-of-rust-racer/) 25 | * [Day 9 - anymap](https://siciarz.net/24-days-of-rust-anymap/) 26 | * [Day 10 - the glorious tau](https://siciarz.net/24-days-of-rust-glorious-tau/) 27 | * [Day 11 - postgres](https://siciarz.net/24-days-of-rust-postgres/) 28 | * [Day 12 - image](https://siciarz.net/24-days-of-rust-image/) 29 | * [Day 13 - uuid](https://siciarz.net/24-days-of-rust-uuid/) 30 | * [Day 14 - nalgebra](https://siciarz.net/24-days-of-rust-nalgebra/) 31 | * [Day 15 - FUSE filesystems, part 1](https://siciarz.net/24-days-of-rust-fuse-filesystems-part-1/) 32 | * [Day 16 - FUSE filesystems, part 2](https://siciarz.net/24-days-of-rust-fuse-filesystems-part-2/) 33 | * [Day 17 - from_fn](https://siciarz.net/24-days-of-rust-from_fn/) 34 | * [Day 18 - redis](https://siciarz.net/24-days-of-rust-redis/) 35 | * [Day 19 - rusti](https://siciarz.net/24-days-of-rust-rusti/) 36 | * [Day 20 - zeromq](https://siciarz.net/24-days-of-rust-zeromq/) 37 | * [Day 21 - rust-crypto](https://siciarz.net/24-days-of-rust-rust-crypto/) 38 | * [Day 22 - built with Rust](https://siciarz.net/24-days-of-rust-built-with-rust/) 39 | * [Day 23 - calling Rust from other languages](https://siciarz.net/24-days-of-rust-calling-rust-from-other-languages/) 40 | * [Day 24 - conclusion](https://siciarz.net/24-days-of-rust-conclusion/) 41 | 42 | Where to go from here? 43 | ---------------------- 44 | 45 | I'm hoping to write more about Rust, so stay tuned. Meanwhile there are a few other resources worth checking out: 46 | 47 | * [discuss.rust-lang.org](http://discuss.rust-lang.org/) 48 | * [This Week in Rust](http://this-week-in-rust.org/) 49 | * [This Week in Servo](http://blog.servo.org/) 50 | * [The /r/rust subreddit](http://www.reddit.com/r/rust) 51 | * [#rustlang on Twitter](https://twitter.com/hashtag/rustlang) 52 | -------------------------------------------------------------------------------- /book/vol1/day3.md: -------------------------------------------------------------------------------- 1 | # Day 3 - csv 2 | 3 | > Relevancy: 1.9 stable 4 | 5 | Most of us programmers have encountered the [CSV format](http://en.wikipedia.org/wiki/Comma-separated_values) at some point of our career. Whether you cooperate with financial people, analyze some scientific data or simply allow the users of your web app to download a record of their activities, chances are you'll use some variation of CSV as the data format. Note that I said *some variation* - CSV itself isn't standardized and there are lots of quirks in different implementations. 6 | 7 | CSV libraries exist for lots of languages, making it a common format for interoperability (alongside XML or JSON) and sometimes preferred for data of a tabular nature. In the Rust ecosystem there is the [csv crate](https://crates.io/crates/csv) which will be the focus of this blog post. 8 | 9 | Writing to CSV 10 | -------------- 11 | 12 | One would think that there's nothing simpler than writing a CSV file. Join the stringified values with commas and that's it, right? Unfortunately it's not that simple, what if the values contain commas, quotes, new line characters etc.? At this point you need a CSV library which knows how to handle all these edge cases. Fortunately the `csv` crate provides a `Writer` type that takes care of all that. 13 | 14 | [include:15-24](../../vol1/src/bin/day3.rs) 15 | 16 | Now let's check the output if the `Writer` handled comma in the last title correctly: 17 | 18 | ```sh 19 | $ cat westerns.csv 20 | A Fistful of Dollars,Rojo,1964 21 | For a Few Dollars More,El Indio,1965 22 | "The Good, the Bad and the Ugly",Tuco,1966 23 | ``` 24 | 25 | Yes! So we can write vectors of things as CSV rows, fine. But what if our application represents the data as some custom type, do we have to build a vector from that? Imagine this is an online movie catalog of some sorts. Having a `Movie` struct with `title`, `bad_guy` fields etc. is a better API design than relying on the order of items in a tuple or vector. 26 | 27 | [include:2-2](../../vol1/src/bin/day3.rs) 28 | [include:6-12](../../vol1/src/bin/day3.rs) 29 | 30 | We need to import the `rustc_serialize` crate so that Rust can derive for us the `RustcEncodable` trait. By the way, this also enables serializing `Movie` objects to JSON. 31 | 32 | [include:25-31](../../vol1/src/bin/day3.rs) 33 | 34 | Try removing the `#[derive(RustcEncodable)]` attribute and see what happens. Turns out the CSV writer can handle anything that implements `RustcEncodable`. 35 | 36 | CSV parsing 37 | ----------- 38 | 39 | Writing CSV is one part of the story. If you're a client of some API that exposes CSV data, you'll need to have a way to read that into some meaningful representation. But define meaningful? Let's start with plain tuples. 40 | 41 | [include:32-36](../../vol1/src/bin/day3.rs) 42 | 43 | We need to give the reader a hint regarding field types. If we changed it for example to `(String, i32, usize)`, `unwrap` would panic with a CSV decode error. However changing `usize` to `String` would work, although we would have to explicitly parse the field as integer. 44 | 45 | ```sh 46 | $ cargo run 47 | ("For a Few Dollars More", "El Indio", 1965) 48 | ("The Good, the Bad and the Ugly", "Tuco", 1966) 49 | ("Hang \'Em High", "Wilson", 1968) 50 | ``` 51 | 52 | Wait, where's the first dollar movie (*A Fistful of Dollars*)? The `Reader` by default considers the first row in a CSV file as headers, which are not exposed in the iterator returned by `decode()`. You can use the `has_headers()` method to disable this behaviour. 53 | 54 | [include:32-32](../../vol1/src/bin/day3.rs) 55 | 56 | ```sh 57 | $ cargo run 58 | ("A Fistful of Dollars", "Rojo", 1964) 59 | ("For a Few Dollars More", "El Indio", 1965) 60 | ("The Good, the Bad and the Ugly", "Tuco", 1966) 61 | ("Hang \'Em High", "Wilson", 1968) 62 | ``` 63 | 64 | There is also a nice symmetry with the `Writer`. We can serialize structs to CSV, so we should be able to read into structs directly. If the struct implements `RustcDecodable` trait (usually by deriving), we can do it! 65 | 66 | [include:37-42](../../vol1/src/bin/day3.rs) 67 | 68 | You can find a few more examples in the [csv crate docs](http://burntsushi.net/rustdoc/csv/). It's also possible to change the delimiter (for example if you have TSV data - tab separated values), quote characters and row separators. I think it would be fantastic if the library allowed for different CSV *dialects*, as does the [Python standard library](https://docs.python.org/3.4/library/csv.html#csv-fmt-params). Other than that, the `csv` crate is definitely usable and quite performant. There are also ways to improve performance even more by giving up on convenient struct manipulation and using low-level field API directly. 69 | 70 | Check out also [xsv](https://github.com/BurntSushi/xsv) - a commandline toolkit for working with CSV data written in Rust. Try reading the source to see how it uses the `csv` crate. 71 | -------------------------------------------------------------------------------- /book/vol1/day4.md: -------------------------------------------------------------------------------- 1 | # Day 4 - docopt 2 | 3 | > Relevancy: 1.9 stable (macros only on nightly) 4 | 5 | One of the few chores when building a commandline program is argument parsing, so that `myprogram --config=myfile.conf --verbose -o output.txt` makes sense. Some arguments come in short and long variants, some are optional and some are positional only. There are a lot of libraries for argument parsing, some are even included in the respective languages' distributions. In Rust's case there's the [getopts crate](https://crates.io/crates/getopts). 6 | 7 | The first thing a moderately savvy user will do is... no, not read the documentation, but run `myprogram -h` (or `--help`) to discover available options. `getopts` and other libraries can derive such help summary for you, saving your time and reducing duplication. But what if it was the other way round? You'd write the usage message, listing possible options, and the tool would build an argument parser from that. Enter [docopt](http://docopt.org/). 8 | 9 | What is docopt? 10 | --------------- 11 | 12 | The **docopt** initiative [originated in the Python community](https://www.youtube.com/watch?v=pXhcPJK5cMc) in 2012 as an attempt to standardize common conventions for commandline arguments. The main idea is that the help message describes the interface of your program. Once you follow a few rules when writing that message, docopt can understand it and build an argument parser. 13 | 14 | docopt for Rust 15 | --------------- 16 | 17 | We'll start by declaring a dependency in `Cargo.toml`: 18 | 19 | ```ini 20 | [dependencies] 21 | docopt = "~0.6.78" 22 | docopt_macros = "~0.6.80" 23 | ``` 24 | 25 | Cargo will now pull the [docopt crate](https://crates.io/crates/docopt) along with the macros which are distributed separately. 26 | 27 | **Note:** The macros use a language feature (syntax extensions) that didn't stabilize in time before 1.0 release. Therefore at present the `docopt!` macro can be used only with the nightly versions of the compiler. 28 | 29 | In our example application we'll try to mimic the `wc` tool for counting lines, words or characters in a file. Let's start with importing required libraries and writing the usage message: 30 | 31 | ```rust 32 | extern crate rustc_serialize; 33 | extern crate docopt; 34 | 35 | use docopt::Docopt; 36 | 37 | static USAGE: &'static str = " 38 | Usage: wc [options] [] 39 | 40 | Options: 41 | -c, --bytes print the byte counts 42 | -m, --chars print the character counts 43 | -l, --lines print the newline counts 44 | -w, --words print the word counts 45 | -L, --max-line-length print the length of the longest line 46 | -h, --help display this help and exit 47 | -v, --version output version information and exit 48 | "; 49 | ``` 50 | 51 | Nothing fancy here, just a long string. See the [docopt specification](http://docopt.org/) for the details of the format. We want to be able to decode the options into a struct, so it makes sense for the struct to implement the `Decodable` trait. 52 | 53 | ```rust 54 | #[derive(RustcDecodable)] 55 | struct Args { 56 | arg_file: Option, 57 | flag_bytes: bool, 58 | flag_chars: bool, 59 | flag_lines: bool, 60 | flag_words: bool, 61 | flag_max_line_length: bool, 62 | } 63 | ``` 64 | 65 | The arguments must map to field names. Flags (toggle switches such as `-c` above) map to `flag_`-prefixed boolean fields, option arguments or meta-variables map to `arg_`-prefixed fields. If the argument is optional it can be represented by an `Option` value. But how do we actually turn the commandline args into a struct? 66 | 67 | ```rust 68 | let args: Args = Docopt::new(USAGE).and_then(|d| d.decode()).unwrap_or_else(|e| e.exit()); 69 | ``` 70 | 71 | Now this is a dense line of code. Let's walk through it one step at a time. 72 | 73 | ```rust 74 | let docopt = match Docopt::new(USAGE) { 75 | Ok(d) => d, 76 | Err(e) => e.exit(), 77 | }; 78 | println!("{:?}", docopt); 79 | let args: Args = match docopt.decode() { 80 | Ok(args) => args, 81 | Err(e) => e.exit(), 82 | }; 83 | ``` 84 | 85 | `Docopt::new()` returns a `Result` value. The errors from docopt have a handy `exit()` method that prints the error message and quits the program. Printing a `Docopt` value gives us a lot of debugging information. The `decode()` method is responsible for creating our arguments object and we extract it from the `Ok` variant. We can now use `args` as any other struct in our program. 86 | 87 | [include:25-31](../../vol1/src/bin/day4.rs) 88 | 89 | docopt_macros 90 | ------------- 91 | 92 | But hey, didn't I tell you earlier about reducing duplication? In the example above there are two sources of information, namely the usage string and the `Args` struct. It's quite clear that these two represent the same concept, but you'll need to mantain two separate code pieces instead of one and that is prone to errors. 93 | 94 | `docopt!` to the rescue! This is a funny (for some value of fun, of course) macro that will generate the struct for us. 95 | 96 | [include:1-18](../../vol1/src/bin/day4.rs) 97 | 98 | The macro takes the name of the type to generate, usage string and (optionally) types for the generated fields. It also validates that the usage message conforms to the docopt spec. The validation happens at compile time when the `Args` struct is generated so there's no runtime overhead. But most importantly we now have a **single** piece of information to maintain instead of two. 99 | 100 | There's one more advantage of the macro - our code inside `main()` can be simplified a bit. As the struct is generated from the usage message, we can get rid of one intermediate `Result` unwrapping; the struct has a static `docopt()` method which returns a `Docopt` value. 101 | 102 | [include:22-24](../../vol1/src/bin/day4.rs) 103 | 104 | Docopt for Rust recently gained an ability to generate tab completion files for the shell (only bash at the moment). See the [readme](https://github.com/docopt/docopt.rs#tab-completion-support) for more on that. 105 | 106 | See also 107 | -------- 108 | 109 | * [using docopt in C](http://kblomqvist.github.io/2013/03/21/creating-beatiful-command-line-interfaces-for-embedded-systems-part1/) for embedded systems 110 | * docopt implementations in various languages under the [docopt organization on GitHub](https://github.com/docopt) 111 | * [wc in Rust](https://github.com/uutils/coreutils/blob/master/src/wc/wc.rs) from the coreutils rewrite project 112 | -------------------------------------------------------------------------------- /book/vol1/day5.md: -------------------------------------------------------------------------------- 1 | # Day 5 - hyper 2 | 3 | > Relevancy: 1.9 stable 4 | 5 | The state of HTTP libraries in Rust was a constant flux before 1.0. However it appears that a specific package won the hearts of Rust programmers: [hyper](https://crates.io/crates/hyper), which will be the subject of this chapter. 6 | 7 | I'm going to focus on using `hyper` only as a client, although the library contains also a server implementation. However with the advance of Rust web frameworks building on top of HTTP libraries, the programmers will focus less on developing servers and more on the clients. Consuming web APIs is a lot more common than writing new shiny servers. How can `hyper` help us? 8 | 9 | Basic requests 10 | -------------- 11 | 12 | Let's start from the usual dependency definition in `Cargo.toml`. 13 | 14 | ```ini 15 | [dependencies] 16 | hyper = "~0.7" 17 | ``` 18 | 19 | When you run `cargo build`, Cargo will download a few other required crates (for URL handling, mimetype support, OpenSSL bindings etc.) and hopefully compile `hyper` afterwards. Time for our first request! 20 | 21 | ```rust 22 | extern crate hyper; 23 | 24 | use std::io::Read; 25 | use hyper::{Client}; 26 | 27 | fn main() { 28 | let client = Client::new(); 29 | let url = "http://httpbin.org/status/201"; 30 | let mut response = match client.get(url).send() { 31 | Ok(response) => response, 32 | Err(_) => panic!("Whoops."), 33 | }; 34 | let mut buf = String::new(); 35 | match response.read_to_string(&mut buf) { 36 | Ok(_) => (), 37 | Err(_) => panic!("I give up."), 38 | }; 39 | println!("buf: {}", buf); 40 | } 41 | ``` 42 | 43 | That was... *verbose*. I could just use `unwrap()` everywhere, but that would be handwaving and in poor taste. Sprinkling your code with `panic!` is not a sign of good style too. However, there are so many things that can go wrong during an HTTP request/response cycle! But there seems to be a pattern. Can we do better? 44 | 45 | [include:10-16](../../vol1/src/bin/day5.rs) 46 | [include:48-48](../../vol1/src/bin/day5.rs) 47 | 48 | We refactored the request cycle into a separate function. But look how the code got simpler, thanks to the [try! macro](http://doc.rust-lang.org/std/result/#the-try!-macro). There's no explicit matching on the `Result` variants and the first `try!` that fails will return from the function with some kind of an HTTP error. 49 | 50 | POST and query parameters 51 | ------------------------- 52 | 53 | Sending POST requests with hyper is only a little bit more complicated. We'll write a wrapper function again, this time taking an additional argument of type `Query`. 54 | 55 | [include:18-27](../../vol1/src/bin/day5.rs) 56 | [include:49-50](../../vol1/src/bin/day5.rs) 57 | 58 | The main difference from `get_content()` is the serialization machinery coming from the [url](https://crates.io/crates/url) crate. Once we've built a raw request body (like `key=value&foo=bar`), we pass it to the `body()` method and the rest is identical to the GET example above. 59 | 60 | Sending JSON 61 | ------------ 62 | 63 | Our `post_query` function can be easily changed to borrow a struct, serialize it to JSON and send it over the wire. 64 | 65 | [include:29-37](../../vol1/src/bin/day5.rs) 66 | 67 | This function is generic in its `payload` argument, accepting anything that implements the `Encodable` trait. We can use the function as follows: 68 | 69 | [include:39-44](../../vol1/src/bin/day5.rs) 70 | [include:51-56](../../vol1/src/bin/day5.rs) 71 | 72 | See also 73 | -------- 74 | 75 | * [HTTP library requirements](https://github.com/servo/servo/wiki/HTTP-library-requirements) from the Servo project 76 | * [rust-request](https://github.com/jgillich/rust-request) - a HTTP client library written on top of hyper 77 | * [rest_client](https://github.com/gtolle/rest_client) - another HTTP client built with hyper 78 | * [Improved error handling in Rust](http://lucumr.pocoo.org/2014/11/6/error-handling-in-rust/) by Armin Ronacher 79 | -------------------------------------------------------------------------------- /book/vol1/day6.md: -------------------------------------------------------------------------------- 1 | # Day 6 - working with JSON 2 | 3 | > Relevancy: 1.9 stable (macros only on nightly) 4 | 5 | [JSON](http://en.wikipedia.org/wiki/JSON) is a workhorse data format of the modern Web. Originating from the JavaScript world, it gained a lot of traction and at the moment it's usually the first choice of a Web developer for a data interchange format. Not only Web - once JavaScript-only, JSON support is now ubiquitous. A lot of languages ship with JSON parsers in the standard libraries, and when it's not the case, surely someone has already built a third party library. In case of Rust, JSON support comes out in the [rustc_serialize::json](http://doc.rust-lang.org/rustc-serialize/rustc_serialize/json/index.html) module. 6 | 7 | **Note**: in this article I deliberately do not focus on the Web, APIs, requests and so on. I mentioned JSON in a [previous post about hyper](http://siciarz.net/24-days-of-rust-hyper/) but now I don't care where the JSON-encoded data comes from or what to do with it later. Here I'm just going to show you a few practical tips. 8 | 9 | Slightly offtopic: I'm writing this blogpost while getting ready to coach a group of fantastic women at [Django Girls](http://djangogirls.org/) in Łódź, Poland. If you're a woman interested in learning programming (or know such girls) check out if there's a Django Girls workshop near you! The workshops focus on basic Python, Django and web technologies, but the general idea is to get the attendees genuinely interested in programming and empowered to create. 10 | 11 | That said, now back to JSON and Rust. 12 | 13 | Primitives 14 | ---------- 15 | 16 | A lot of Rust types serialize to JSON just as you would expect. Note that `encode()` immutably borrows its argument: 17 | 18 | [include:4-7](../../vol1/src/bin/day6.rs) 19 | [include:26-28](../../vol1/src/bin/day6.rs) 20 | 21 | `Option` maps to the encoding of `value` itself if it is a `Some(value)` while `None` maps to `null`. 22 | 23 | Automatic (de)serialization 24 | --------------------------- 25 | 26 | In the [chapter on CSV](day3.md) I mentioned the `RustcEncodable` and `RustcDecodable` traits. Here's an example with a nested struct: 27 | 28 | [include:29-38](../../vol1/src/bin/day6.rs) 29 | 30 | ```sh 31 | $ cargo run 32 | Ok("{\"name\":\"Zbyszek\",\"post_count\":100,\"likes_burgers\":true,\"avatar\":{\"url\":\"http://lorempixel.com/160/160/\",\"dimensions\":[160,160]}}") 33 | ``` 34 | 35 | Pretty printing 36 | --------------- 37 | 38 | The `json::encode()` doesn't care for readability of it's output. Although the JSON it emits is correct and machine-readable, there are no newlines or indents making it hard for a human to debug. Pretty-printing is a little bit more complex than just one function call, but not too complicated: 39 | 40 | [include:39-44](../../vol1/src/bin/day6.rs) 41 | 42 | Decoding 43 | -------- 44 | 45 | [include:45-49](../../vol1/src/bin/day6.rs) 46 | 47 | As you cen see, decoding is also pretty easy. But what happens if we don't know all of the fields in advance? We can use another function in the `json` module - `from_str()`. The difference between `from_str()` and `decode()` is that the latter may return some struct implementing `RustcDecodable` while the former returns a [Json](http://doc.rust-lang.org/rustc-serialize/rustc_serialize/json/enum.Json.html) value. This type has a few methods of its own, including `find()`. See the example below: 48 | 49 | [include:50-57](../../vol1/src/bin/day6.rs) 50 | 51 | We're using the [if let](http://doc.rust-lang.org/book/if-let.html) language construct which often simplifies pattern matches where we care for only one branch and do nothing if the expression doesn't match. 52 | 53 | The json! macro 54 | --------------- 55 | 56 | > **Note:** syntax extensions work only on nightly 57 | 58 | There's one more thing I wanted to show you today. You can embed JSON-like literals directly in your Rust code with the help of the [json_macros](https://crates.io/crates/json_macros) crate. This is a compiler extension that allows for some nice syntactic sugar like below: 59 | 60 | [include:1-2](../../vol1/src/bin/day6.rs) 61 | [include:58-68](../../vol1/src/bin/day6.rs) 62 | -------------------------------------------------------------------------------- /book/vol1/day7.md: -------------------------------------------------------------------------------- 1 | # Day 7 - itertools 2 | 3 | > Relevancy: 1.9 stable 4 | 5 | The [itertools crate](https://crates.io/crates/itertools) contains several utility functions and macros inspired by Haskell and [Python itertools](https://docs.python.org/3/library/itertools.html). As you can guess from the name, these have to do with iteration and iterators. 6 | 7 | To use `itertools`, add the following dependency declaration to `Cargo.toml`: 8 | 9 | ```ini 10 | [dependencies] 11 | itertools = "~0.0.4" 12 | ``` 13 | 14 | We'll start from the helper functions and cover the macros later. 15 | 16 | foreach 17 | ----- 18 | 19 | This and a few other functions live in the `Itertools` trait, so we need to bring them into scope by placing `use itertools::Itertools` in our module. 20 | 21 | `foreach()` is very simple conceptually. It consumes the iterator, calling a closure witch each of the elements. The return type is `()` (unit), meaning that `foreach()` usually should be at the end of a call chain, like below: 22 | 23 | [include:8-9](../../vol1/src/bin/day7.rs) 24 | 25 | As you can see, `foreach()` is similar to the `map()` method from the standard library, however `map` returns another iterator. Therefore it's lazy and allows for further method chaining, while `foreach` is eager and has the final word. 26 | 27 | interleave and intersperse 28 | -------------------------- 29 | 30 | `interleave()` is somewhat similar to `zip()`. But when `zip` builds tuples from two iterators, `interleave` yields the values alternating between both iterators. 31 | 32 | [include:10-12](../../vol1/src/bin/day7.rs) 33 | 34 | The result: 35 | 36 | ```sh 37 | $ cargo run 38 | [2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18] 39 | ``` 40 | 41 | As you can see, the iterators don't have to contain the same number of elements. In that case `interleave` continues to emit values from the other iterator, after it consumes the "shorter" one. 42 | 43 | In a manner similar to its [Haskell counterpart](http://hackage.haskell.org/package/base-4.7.0.1/docs/Data-List.html#v:intersperse), `intersperse` takes a single value and an iterator (implicitly as the `self` argument) and emits the given value between every element of the wrapped iterator. For example: 44 | 45 | [include:13-13](../../vol1/src/bin/day7.rs) 46 | 47 | Output: 48 | 49 | ```sh 50 | $ cargo run 51 | [1, 15, 2, 15, 3, 15, 4, 15, 5, 15, 6, 15, 7, 15, 8, 15, 9] 52 | ``` 53 | 54 | iproduct! 55 | --------- 56 | 57 | Let's now turn our attention to macros provided by `itertools`. Sometimes there is a need to iterate over a cartesian product of some lists/arrays/vectors. Usually it involves two nested loops; however we can use the `iproduct()` macro to simplify it to a single `for` loop. 58 | 59 | [include:14-18](../../vol1/src/bin/day7.rs) 60 | -------------------------------------------------------------------------------- /book/vol1/day8.md: -------------------------------------------------------------------------------- 1 | # Day 8 - racer 2 | 3 | > Relevancy: 1.9 stable 4 | 5 | Welcome to the second week of 24 days of Rust! Hope you enjoy the articles so far. Today let me introduce you to [racer](https://github.com/phildawes/racer) - a code completion engine for Rust. 6 | 7 | As there is no proper Rust IDE (yet), most of us [Rustaceans](http://www.rustaceans.org/) use some code editor such as Vim, Emacs, Atom etc. However the out-of-the-box support for Rust is very limited - if you're lucky, your editor has syntax highlighting, but not much more. We'd love to have some sort of autocompletion to ease typing, and a "go to definition" command to quickly jump around the codebase. 8 | 9 | `racer` is a tool (still in development) to do just that. Follow the [instructions in readme](https://github.com/phildawes/racer) to compile it. You'll need to have the source code for Rust compiler and standard libraries on your machine. Fortunately it's just a `git clone` away: 10 | 11 | ```sh 12 | $ git clone https://github.com/rust-lang/rust.git $HOME/rust 13 | $ export RUST_SRC_PATH=$HOME/rust/src 14 | ``` 15 | 16 | The second line sets an environment variable that `racer` uses to find Rust sources. You can put that line in your `.bashrc`, `.zshrc` or whatever startup file your shell uses. 17 | 18 | The `racer` executable has a few subcommands (you can see the full list by running `racer` with no arguments). Let's take a look at the most important ones. 19 | 20 | Completion engine 21 | ----------------- 22 | 23 | The `racer complete` subcommand takes a part of some (fully qualified) Rust name and tries to find completions. For example: 24 | 25 | ```sh 26 | $ ./target/release/racer complete std::option:: 27 | MATCH Option,167,9,/home/zbyszek/Development/Rust/rust/src/libcore/option.rs,Enum,pub enum Option { 28 | MATCH Iter,815,11,/home/zbyszek/Development/Rust/rust/src/libcore/option.rs,Struct,pub struct Iter<'a, A: 'a> { inner: Item<&'a A> } 29 | MATCH IterMut,845,11,/home/zbyszek/Development/Rust/rust/src/libcore/option.rs,Struct,pub struct IterMut<'a, A: 'a> { inner: Item<&'a mut A> } 30 | MATCH IntoIter,868,11,/home/zbyszek/Development/Rust/rust/src/libcore/option.rs,Struct,pub struct IntoIter { inner: Item } 31 | 32 | ``` 33 | 34 | The output is intended to be computer-readable, in fact you could possibly parse it as a comma-separated format (see the [chapter on CSV](day3.md)). 35 | 36 | ![racer completion in Vim](//i.imgur.com/gE3Q7i4.png) 37 | 38 | Find definition 39 | --------------- 40 | 41 | The second important subcommand is `racer find-definition`. It expects three extra arguments: line number, column number and a filename. The first two numbers are cursor coordinates, so from the code editor's point of view `racer` will try to find the definition of the word under cursor. For example when run on `racer`'s own source (the coordinates point to a `Vec` usage): 42 | 43 | ```sh 44 | $ ./target/release/racer find-definition 56 15 src/main.rs 45 | MATCH Vec,154,11,/home/zbyszek/Development/Rust/rust/src/libcollections/vec.rs,Struct,pub struct Vec { 46 | 47 | ``` 48 | 49 | The output format is the same as in the case of `racer complete`. See how that works in Vim when I press `gd` with the cursor on the `Option` word: 50 | 51 | ![find definition in Vim](//i.imgur.com/fyE80H5.gif) 52 | 53 | Editor integrations 54 | ------------------- 55 | 56 | TODO: write about YouCompleteMe 57 | 58 | [Vim](https://github.com/phildawes/racer/blob/master/editors/racer.vim) and [Emacs](https://github.com/phildawes/racer/blob/master/editors/racer.el) plugins are bundled with `racer`'s source. For other editors there are some third-party plugins: 59 | 60 | * [atom-racer](https://github.com/edubkendo/atom-racer) for [Atom](https://atom.io/) 61 | * [RustAutoComplete](https://sublime.wbond.net/packages/RustAutoComplete) for [Sublime Text](http://www.sublimetext.com/) 62 | -------------------------------------------------------------------------------- /book/vol1/day9.md: -------------------------------------------------------------------------------- 1 | # Day 9 - anymap 2 | 3 | > Relevancy: 1.9 stable 4 | 5 | In this article we will focus on the [anymap](https://crates.io/crates/anymap) crate by [Chris Morgan](http://chrismorgan.info/) of `rust-http` and `teepee` fame. This crate provides the `AnyMap` type - a slightly peculiar, interesting container. 6 | 7 | The `AnyMap` type is different from a regular map. For example, a `HashMap` in Rust is a generic type parametrized by `K` - the type of keys and `V` - the type of values stored in map. (There's also a hasher parameter, but it's not relevant here.) Meanwhile, `AnyMap` itself is **not** a generic type. It uses a `HashMap` internally but we don't need to know that; conceptually, `AnyMap` maps from *types* to values. This means that for each and every type there can be at most one value contained in the mapping. 8 | 9 | You may ask - why would I ever need something that holds just one value of each type? A lot of programs use some kind of a map from (usually) strings to some arbitrary values, for example to store configuration data, process environment etc. Let me quote Chris on that: 10 | 11 | > It’s typically something like map[string]interface{} and is accessed with arbitrary strings which may clash and type assertions which are a little unwieldy and must be used very carefully. (Personally I would consider that it is just asking for things to blow up in your face.) In a language like Go, lacking in generics, this is the best that can be done; such a thing cannot possibly be made safe without generics. 12 | 13 | We can use `AnyMap` together with the [newtype idiom](http://aturon.github.io/features/types/newtype.html) to create a strongly typed configuration holder. 14 | 15 | [include:1-16](../../vol1/src/bin/day9.rs) 16 | [include:20-27](../../vol1/src/bin/day9.rs) 17 | 18 | The output: 19 | 20 | ```sh 21 | $ cargo run 22 | Some(DomainName("siciarz.net")) 23 | Some(Port(666)) 24 | ``` 25 | 26 | Here the `Port` and `ConnectionLimit` types are abstractions over the underlying integer (with no overhead at runtime!). It is also impossible to mix these two - they are totally different types (not aliases to `u32`) and you can't pass a `Port` value where a `ConnectionLimit` is expected. This fact suggests that we can use these as separate entries in the `AnyMap`. And that is correct, as shown in the example above. It's also worth noting that inserting a value wrapped in a newtype does not make the original type appear in the mapping (seems obvious, I guess). 27 | 28 | When we insert another value of a type that already exists in the `AnyMap`, the previous value gets overwritten. Even if this is another enum variant - as enum variants are values grouped under one type - and remember we think of `AnyMap` as mapping from *types* to values. 29 | 30 | [include:28-29](../../vol1/src/bin/day9.rs) 31 | 32 | ```sh 33 | $ cargo run 34 | Some(Ip(127.0.0.1)) 35 | ``` 36 | 37 | Generic types are considered different for every type parameter, so for example every `Option`-al type gets a separate entry in the `AnyMap`. 38 | 39 | [include:30-40](../../vol1/src/bin/day9.rs) 40 | -------------------------------------------------------------------------------- /book/vol2/README.md: -------------------------------------------------------------------------------- 1 | Volume 2 2 | ======== 3 | 4 | Welcome to the second edition of 24 Days of Rust! 5 | -------------------------------------------------------------------------------- /book/vol2/day13.md: -------------------------------------------------------------------------------- 1 | # Day 13 - zip and lzma compression 2 | 3 | The [`zip`](https://crates.io/crates/zip) crate is the most commonly used 4 | Rust library for manipulating ZIP archives. It supports reading and writing 5 | .zip files with different compression methods (store, deflate, bzip2). 6 | 7 | There are at least three crates for LZMA (de)compression on crates.io. 8 | [`lzma`](https://crates.io/crates/lzma) is pure Rust, but currently allows 9 | only reading from archives. 10 | [`rust-lzma`](https://crates.io/crates/rust-lzma) supports both reading 11 | and writing compressed data, but it's a wrapper for `liblzma`. Similarly 12 | the [`xz2`](https://crates.io/crates/xz2) crate is a binding for `liblzma`. 13 | 14 | Creating archives 15 | ----------------- 16 | 17 | We're going to compress a text file, namely the `Cargo.lock` file which 18 | exists in every `cargo` project and keeps track of precise versions 19 | of dependencies. This is for illustration only, the lock file doesn't grow 20 | so much it would require compression. 21 | 22 | ```rust 23 | static FILE_CONTENTS: &'static [u8] = include_bytes!("../../Cargo.lock"); 24 | ``` 25 | 26 | The `include_bytes!` macro comes from the standard library and allows 27 | for embedding literal arrays of bytes in the source code. 28 | 29 | ```rust 30 | extern crate zip; 31 | 32 | use std::io::{Seek, Write}; 33 | use zip::result::ZipResult; 34 | use zip::write::{FileOptions, ZipWriter}; 35 | 36 | fn create_zip_archive(buf: &mut T) -> ZipResult<()> { 37 | let mut writer = ZipWriter::new(buf); 38 | writer.start_file("example.txt", FileOptions::default())?; 39 | writer.write(FILE_CONTENTS)?; 40 | writer.finish()?; 41 | Ok(()) 42 | } 43 | ``` 44 | 45 | The `zip` crate exposes a `ZipWriter` struct which wraps anything that's 46 | `Seek + Write` (a file, stdout, an in-memory buffer etc). 47 | 48 | ```rust 49 | fn main() { 50 | let mut file = File::create("example.zip").expect("Couldn't create file"); 51 | create_zip_archive(&mut file).expect("Couldn't create archive"); 52 | } 53 | ``` 54 | 55 | After running this, we should now have an `example.zip` file in the current 56 | directory. You can verify with `unzip` or a GUI archive reader like 7-Zip 57 | that it contains correct data. 58 | 59 | Reading ZIP archives 60 | -------------------- 61 | 62 | In the same vein as `ZipWriter` wraps a writable object, the `ZipArchive` 63 | is a wrapper around `Read + Seek`. We can use it to read archive contents 64 | like in the example below: 65 | 66 | ```rust 67 | fn browse_zip_archive(buf: &mut T, browse_func: F) -> ZipResult> 68 | where T: Read + Seek, 69 | F: Fn(&ZipFile) -> ZipResult 70 | { 71 | let mut archive = ZipArchive::new(buf)?; 72 | (0..archive.len()) 73 | .map(|i| archive.by_index(i).and_then(|file| browse_func(&file))) 74 | .collect() 75 | } 76 | ``` 77 | 78 | The `browse_zip_archive` function goes through all files in the archive and 79 | applies a callback function to each one. This flexibility allows the caller 80 | to decide what to do with each file in turn. The values returned by the 81 | callback are collected into a `Vec` and returned if all goes well. We're 82 | using a clever trick here: `Result` 83 | [implements `FromIterator`](https://doc.rust-lang.org/std/result/enum.Result.html#method.from_iter). 84 | This means we can turn an iterator of `Result`s into a `Result` wrapping a 85 | container (`Vec` here) with a single call to `collect()`. And if any element 86 | is an `Err`, the `Err` is returned from the entire function. 87 | 88 | ```rust 89 | let mut file = File::open("example.zip").expect("Couldn't open file"); 90 | let files = browse_zip_archive(&mut file, |f| { 91 | Ok(format!("{}: {} -> {}", f.name(), f.size(), f.compressed_size())) 92 | }); 93 | println!("{:?}", files); 94 | ``` 95 | 96 | ```text 97 | $ cargo run 98 | Ok(["example.txt: 66386 -> 10570"]) 99 | ``` 100 | 101 | Other archive formats 102 | --------------------- 103 | 104 | ```rust 105 | fn create_bz2_archive(buf: &mut T) -> ZipResult<()> { 106 | let mut writer = ZipWriter::new(buf); 107 | writer.start_file("example.txt", 108 | FileOptions::default().compression_method(zip::CompressionMethod::Bzip2))?; 109 | writer.write(FILE_CONTENTS)?; 110 | writer.finish()?; 111 | Ok(()) 112 | } 113 | ``` 114 | 115 | We can use `zip` to create a BZIP2 archive. The only change is in the 116 | compression method used by `ZipWriter`. 117 | 118 | And now let's use the `rust-lzma` crate to compress our file to an `.xz` 119 | archive. 120 | 121 | ```rust 122 | use lzma::{LzmaWriter, LzmaError}; 123 | 124 | fn create_xz_archive(buf: &mut T) -> Result<(), LzmaError> { 125 | let mut writer = LzmaWriter::new_compressor(buf, 6)?; 126 | writer.write(FILE_CONTENTS)?; 127 | writer.finish()?; 128 | Ok(()) 129 | } 130 | ``` 131 | 132 | LZMA compression doesn't require the buffer to be seekable, it just emits a 133 | stream of compressed bytes as it goes over the input. The other difference 134 | is that `LzmaWriter` supports different compression levels (6 is typically 135 | the default). 136 | 137 | Comparison 138 | ---------- 139 | 140 | We may be interested in space efficiency of various compression methods. 141 | Let's use the [`metadata`](https://doc.rust-lang.org/std/fs/fn.metadata.html) 142 | function to retrieve size of each file: 143 | 144 | ```rust 145 | if let Ok(meta) = metadata("example.zip") { 146 | println!("ZIP file size: {} bytes", meta.len()); 147 | } 148 | if let Ok(meta) = metadata("example.bz2") { 149 | println!("BZ2 file size: {} bytes", meta.len()); 150 | } 151 | if let Ok(meta) = metadata("example.xz") { 152 | println!("XZ file size: {} bytes", meta.len()); 153 | } 154 | ``` 155 | 156 | ```text 157 | $ cargo run 158 | ZIP file size: 10696 bytes 159 | BZ2 file size: 8524 bytes 160 | XZ file size: 9154 bytes 161 | ``` 162 | 163 | Further reading 164 | --------------- 165 | 166 | - [tar-rs](https://github.com/alexcrichton/tar-rs) 167 | - [brotli](https://crates.io/crates/brotli) 168 | - [Comparison of archive formats](https://en.wikipedia.org/wiki/Comparison_of_archive_formats) 169 | - [Gzip vs Bzip2 vs XZ Performance Comparison](https://www.rootusers.com/gzip-vs-bzip2-vs-xz-performance-comparison/) 170 | -------------------------------------------------------------------------------- /book/vol2/day19.md: -------------------------------------------------------------------------------- 1 | # Day 19 - leftpad 2 | 3 | Do you remember the time when 4 | [one developer broke half of the Internet](http://www.theregister.co.uk/2016/03/23/npm_left_pad_chaos/)? 5 | Earlier this year a package called 6 | [`left-pad`](https://www.npmjs.com/package/left-pad) was pulled from the NPM 7 | registry (a counterpart of crates.io for the JavaScript folks). This caused... 8 | some drama, to say the least. Shall we try this in Rust? Not sure about the 9 | drama, maybe just padding a string on the left is enough. There's a crate for 10 | that - [`leftpad`](https://crates.io/crates/leftpad)! 11 | 12 | Left pad all the things! 13 | ------------------------ 14 | 15 | Look at this beauty. 16 | 17 | ```rust 18 | extern crate leftpad; 19 | 20 | use leftpad::{left_pad, left_pad_char}; 21 | 22 | fn main() { 23 | println!("{}", left_pad("pad me", 20)); 24 | println!("{}", left_pad("pad me again", 20)); 25 | } 26 | ``` 27 | 28 | ```text 29 | $ cargo run 30 | pad me 31 | pad me again 32 | ``` 33 | 34 | Just look. 35 | 36 | ```rust 37 | println!("{}", left_pad_char("tick", 20, '✓')); 38 | ``` 39 | 40 | ```text 41 | $ cargo run 42 | ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓tick 43 | ``` 44 | 45 | Wow. Such characters. Very padding. Wow. 46 | 47 | So what would happen if Hubert decided to yank `leftpad` from crates.io? 48 | Fortunately it's not a big deal. Existing projects using `leftpad` would 49 | still work. We only wouldn't be able to use it for any new applications. 50 | 51 | Let me quote an important fragment of Cargo docs: 52 | 53 | > The semantics of a yanked version are that no new dependencies can be created 54 | > against that version, but all existing dependencies continue to work. One of 55 | > the major goals of crates.io is to act as a permanent archive of crates that 56 | > does not change over time, and allowing deletion of a version would go 57 | > against this goal. Essentially a yank means that all projects with a 58 | > Cargo.lock will not break, while any future Cargo.lock files generated will 59 | > not list the yanked version. 60 | 61 | Thankfully, `leftpad` is licensed under a 62 | [permissive license](https://github.com/hfiguiere/leftpad-rs/blob/3be4768d7d697654184389fcf527f8750ec8596d/LICENSE), 63 | which means I can get away with posting here the entire two functions 64 | (and the original copyright notice to comply with the BSD license terms). 65 | 66 | ```rust 67 | pub fn left_pad(s: &str, pad: usize) -> String 68 | { 69 | left_pad_char(s, pad, ' ') 70 | } 71 | 72 | pub fn left_pad_char(s: &str, pad: usize, padchar: char) -> String 73 | { 74 | let mut out = String::new(); 75 | 76 | let len = s.len(); 77 | if pad > len { 78 | for _ in 0..pad-len { 79 | out.push(padchar); 80 | } 81 | } 82 | out.push_str(s); 83 | 84 | out 85 | } 86 | ``` 87 | 88 | ```text 89 | Copyright (c) 2016, Hubert Figuière 90 | All rights reserved. 91 | 92 | Redistribution and use in source and binary forms, with or without 93 | modification, are permitted provided that the following conditions are 94 | met: 95 | 96 | 1. Redistributions of source code must retain the above copyright 97 | notice, this list of conditions and the following disclaimer. 98 | 99 | 2. Redistributions in binary form must reproduce the above copyright 100 | notice, this list of conditions and the following disclaimer in the 101 | documentation and/or other materials provided with the distribution. 102 | 103 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 104 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 105 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 106 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 107 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 108 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 109 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 110 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 111 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 112 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 113 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 114 | ``` 115 | 116 | So if anything catastrophic ever happens to GitHub and crates.io, feel free 117 | to come here for your left-padding needs! 118 | 119 | Further reading 120 | --------------- 121 | 122 | - [left-pad as a service](http://left-pad.io/) 123 | - [Could Rust have a left-pad incident?](http://edunham.net/2016/03/24/could_rust_have_a_left_pad_incident.html) 124 | - [docs for `cargo yank`](http://doc.crates.io/crates-io.html#cargo-yank) -------------------------------------------------------------------------------- /book/vol2/day22.md: -------------------------------------------------------------------------------- 1 | # Day 22 - lettre 2 | 3 | [`lettre`](https://crates.io/crates/lettre) is a library to send emails over 4 | SMTP from our Rust applications. Like many other crates in the growing, 5 | but still young Rust ecosystem, it is still a work in progress. But while 6 | there are a few features missing from `lettre`, we can send some emails 7 | right now! 8 | 9 | Just send the email already 10 | --------------------------- 11 | 12 | Let's start with something simple: send a plain *Hello Rust* email. Here's 13 | our first approach with `lettre`: 14 | 15 | ```rust 16 | extern crate lettre; 17 | 18 | use lettre::email::EmailBuilder; 19 | use lettre::transport::EmailTransport; 20 | 21 | fn main() { 22 | let email = EmailBuilder::new() 23 | .from("zbigniew@siciarz.net") 24 | .to("zbigniew@siciarz.net") 25 | .subject("Hello Rust!") 26 | .body("Hello Rust!") 27 | .build() 28 | .expect("Failed to build message"); 29 | let mut transport = smtp::SmtpTransportBuilder::localhost() 30 | .expect("Failed to create transport") 31 | .build(); 32 | println!("{:?}", transport.send(email.clone())); 33 | } 34 | ``` 35 | 36 | The `EmailBuilder` exposes a fluent API to create an email message. Apart 37 | from the methods shown above, we can set additional headers, CC and reply 38 | addresses, or an HTML body. Finally the `build()` method ends the chain 39 | and returns a `Result`. So what happens if we run this example? 40 | 41 | ```text 42 | $ cargo run 43 | Err(Io(Error { repr: Os { code: 111, message: "Connection refused" } })) 44 | ``` 45 | 46 | Oops... But notice that we used `localhost()` in the transport configuration. 47 | More often than not we won't have an SMTP server installed and running on 48 | our local machine. Let's use GMail as our email backend instead: 49 | 50 | ```rust 51 | use std::env; 52 | 53 | let mut transport = smtp::SmtpTransportBuilder::new(("smtp.gmail.com", smtp::SUBMISSION_PORT)) 54 | .expect("Failed to create transport") 55 | .credentials(&env::var("GMAIL_USERNAME").unwrap_or("user".to_string())[..], 56 | &env::var("GMAIL_PASSWORD").unwrap_or("password".to_string())[..]) 57 | .build(); 58 | println!("{:?}", transport.send(email.clone())); 59 | ``` 60 | 61 | We're reading GMail credentials from environment variables in order not to 62 | hardcode them in our program. If all goes well, after running this code 63 | we should receive an email! 64 | 65 | The `SendableEmail` trait 66 | ------------------------- 67 | 68 | We don't need to use `EmailBuilder` every time we want to send an email. 69 | The `send()` method of the transport requires its argument to implement 70 | `SendableEmail`, nothing else. For example, if we have a custom reporting 71 | system that already has a notion of a report's recipient, we can implement 72 | `SendableEmail` for the `Report` type and send that instead. 73 | 74 | ```rust 75 | extern crate uuid; 76 | 77 | use lettre::email::SendableEmail; 78 | 79 | struct Report { 80 | contents: String, 81 | recipient: String, 82 | } 83 | 84 | impl SendableEmail for Report { 85 | fn from_address(&self) -> String { 86 | "complicated.report.system@gmail.com".to_string() 87 | } 88 | 89 | fn to_addresses(&self) -> Vec { 90 | vec![self.recipient.clone()] 91 | } 92 | 93 | fn message(&self) -> String { 94 | format!("\nHere's the report you asked for:\n\n{}", self.contents) 95 | } 96 | 97 | fn message_id(&self) -> String { 98 | uuid::Uuid::new_v4().simple().to_string() 99 | } 100 | } 101 | 102 | let report = Report { 103 | contents: "Some very important report".to_string(), 104 | recipient: "zbigniew@siciarz.net".to_string(), 105 | }; 106 | transport.send(report).expect("Failed to send the report"); 107 | ``` 108 | 109 | Frankly speaking, this trait doesn't feel like it has a complete API yet. 110 | Most notably, there's no way to set the subject. I hope it's just an oversight 111 | and a relevant method will be added to the trait in some future version of 112 | `lettre`. 113 | 114 | There are a few more missing things in this crate. 115 | At the moment, the most important feature that's not implemented is the 116 | ability to attach files to emails. However, this is being worked on. Also, 117 | a [Mailgun backend](https://github.com/lettre/lettre/issues/108) is already 118 | in progress. 119 | 120 | Remember that `lettre` is still at a 0.x version number, but it's gaining 121 | traction. And hopefully gaining more contributors as well :) 122 | 123 | Further reading 124 | --------------- 125 | 126 | - [`mailstrom`](https://crates.io/crates/mailstrom) - a crate using `lettre` 127 | for mass email delivery 128 | - [`sendgrid`](https://crates.io/crates/sendgrid) a Rust client for SendGrid API 129 | -------------------------------------------------------------------------------- /book/vol2/day23.md: -------------------------------------------------------------------------------- 1 | # Day 23 - built with Rust 2 | 3 | Today's article is another throwback to the first edition of *24 days of Rust*. 4 | In one of the final posts I 5 | [wrote about interesting projects](http://siciarz.net/24-days-of-rust-built-with-rust/) 6 | built with Rust. That was even before the language hit version 1.0. 7 | Two years later and we're at 1.14. [Servo](https://github.com/servo/servo/), 8 | [iota](https://github.com/gchp/iota) and 9 | [coreutils](https://github.com/uutils/coreutils) are still going strong. 10 | It's surely worth checking them out again, but I'm going to introduce a few 11 | more Rust projects that emerged during the last two years. 12 | 13 | Redox 14 | ----- 15 | 16 | How about starting with an **entire operating system** written in Rust? Because 17 | this is what [Redox](https://www.redox-os.org/) is about. It consists of: 18 | 19 | - a Unix-like microkernel 20 | - file system (RedoxFS) 21 | - userspace drivers 22 | - networking 23 | - GUI (Orbital) 24 | - and more... 25 | 26 | All built with Rust, free and open source! An impressive achievement for sure. 27 | While I wouldn't replace my Linux installs with Redox just yet, there are 28 | [.iso releases](https://github.com/redox-os/redox/releases) already. It should 29 | work in QEMU or VirtualBox. 30 | 31 | Project website: [https://www.redox-os.org/](https://www.redox-os.org/) 32 | 33 | ripgrep 34 | ------- 35 | 36 | [Andrew Gallant](https://github.com/BurntSushi), aka `burntsushi`, is a 37 | *prolific* contributor to the Rust library ecosystem. Regexes, CSV, 38 | byte ordering, docopt, property-based testing - Andrew's your man. 39 | This time he took a stab at filesystem search, with an impressive outcome. 40 | [`ripgrep`](https://github.com/BurntSushi/ripgrep) is a CLI search tool 41 | that aims to surpass `grep`, `ack`, `ag` and others in both usability and 42 | performance. In the aptly titled introductory blog post - 43 | [ripgrep is faster than {grep, ag, git grep, ucg, pt, sift}](http://blog.burntsushi.net/ripgrep/) - 44 | Andrew explains his motivation and presents performance benchmarks. 45 | 46 | TL;DR - `ripgrep` is **fast**. Try it out! 47 | 48 | Project website: [https://github.com/BurntSushi/ripgrep](https://github.com/BurntSushi/ripgrep) 49 | 50 | panopticon 51 | ---------- 52 | 53 | I learned about [panopticon](https://panopticon.re/) at 54 | [RustFest EU 2016](http://www.rustfest.eu/talks/#panopticon-a-libre-cross-platform-disassembler-in-rust). 55 | It's a GUI disassembler (for x86, AMD64, AVR and 6502) that aims to be a free, 56 | easy to use and powerful alternative to proprietary tools such as IDA. 57 | Panopticon was rewritten from C++ to Rust and the author expresses his joy 58 | [in a reddit thread](https://www.reddit.com/r/rust/comments/4ihtfa/panopticon_a_libre_crossplatform_disassembler/): 59 | 60 | > Programming in Rust is not only more fun, it's definitely easier too. 61 | > Panopticon used alot sum types that were implemented using boost::variant. 62 | > Like everything with Boost it kind of worked but was incredible ugly and 63 | > complex. Replacing them with enums was probably the biggest reason I switched 64 | > to Rust. Also I found iterator invalidation bugs simply by translating C++ 65 | > to Rust, thanks Borrow Checker! 66 | 67 | Project website: [https://panopticon.re/](https://panopticon.re/) 68 | 69 | Mozilla Firefox 70 | --------------- 71 | 72 | Well, this may be a little stretch ;-) But just a little. Firefox itself wasn't 73 | suddenly rewritten in its entirety from C++ to Rust. However, there are already 74 | [some parts of the browser](https://hacks.mozilla.org/2016/07/shipping-rust-in-firefox/) 75 | built with Rust. [Stylo](https://wiki.mozilla.org/Stylo) is an upcoming 76 | bridge between Firefox and Servo's CSS engine. And with 77 | [Project Quantum](https://medium.com/mozilla-tech/a-quantum-leap-for-the-web-a3b7174b3c12#.kcpq0q16m) 78 | even more good stuff is coming! 79 | 80 | Now it's your turn! 81 | ------------------- 82 | 83 | I'd love to see **you** building awesome Rust projects! Pick a problem that 84 | you'd like to solve and try doing that with Rust. It doesn't have to be an 85 | entire new operating system (that was a hell of an itch to scratch, 86 | Redox folks, right?). 87 | 88 | Here are some inspirations: 89 | 90 | - [Awesome Rust](https://github.com/kud1ing/awesome-rust) - a collection of 91 | programs, libraries and resources 92 | - [Contributing to Rust — libraries](https://www.rust-lang.org/en-US/contribute-libs.html) 93 | - [Are we Web yet?](http://www.arewewebyet.org/) - some areas aren't as green 94 | as I'd like them to be 95 | - [A list of practical projects that anyone can solve in any programming language](https://github.com/karan/Projects) 96 | - [Where do you get ideas to build side-projects?](https://news.ycombinator.com/item?id=5234692) - 97 | a Hacker News discussion 98 | 99 | To summarize this article: 100 | 101 | > Getting a project featured in *24 days of Rust* is left as an exercise for 102 | > the reader. 103 | 104 | Further reading 105 | --------------- 106 | 107 | - [Awesome Rust](https://github.com/kud1ing/awesome-rust) - includes applications written in Rust 108 | - [Friends of Rust](https://www.rust-lang.org/en-US/friends.html) - Organizations running Rust in production 109 | - [ripgrep code review](http://blog.mbrt.it/2016-12-01-ripgrep-code-review/) 110 | - [Quantum](https://wiki.mozilla.org/Quantum) at Mozilla Wiki 111 | -------------------------------------------------------------------------------- /book/vol2/day5.md: -------------------------------------------------------------------------------- 1 | # Day 5 - environment variables 2 | 3 | > Environment variables are a set of dynamic named values that can affect the 4 | > way running processes will behave on a computer. 5 | 6 | That's Wikipedia. Let's read it again. *Dynamic* - because they can change. 7 | *Named* - because like any other variables, they have names. 8 | *Affect processes* - this is the most important part. Environment variables 9 | tell the program in what context it is running - what's the current language, 10 | where is user's home directory etc. They can also store configuration for 11 | the process. For example, a popular cloud hosting platform 12 | ([Heroku](https://devcenter.heroku.com/articles/config-vars)) exposes 13 | configuration values to the app as environment variables. 14 | 15 | Standard library 16 | ---------------- 17 | 18 | The simplest way to access environment variables from Rust is to use the 19 | built-in [`std::env`](https://doc.rust-lang.org/std/env/) module. It exposes 20 | a few useful functions, such as `vars()` which returns an iterator over 21 | all environment variables. In the example below we're using `var()` to read 22 | current language setting. 23 | 24 | ```rust 25 | use std::env; 26 | 27 | fn main() { 28 | match env::var("LANG") { 29 | Ok(lang) => println!("Language code: {}", lang), 30 | Err(e) => println!("Couldn't read LANG ({})", e), 31 | }; 32 | } 33 | ``` 34 | 35 | ```text 36 | $ cargo run 37 | Language code: pl_PL.UTF-8 38 | ``` 39 | 40 | envy 41 | ---- 42 | 43 | [`envy`](https://crates.io/crates/envy) is a small crate that uses `serde` 44 | to automatically deserialize process environment into a Rust struct. 45 | 46 | ```rust 47 | #![feature(proc_macro)] 48 | 49 | extern crate envy; 50 | 51 | #[macro_use] 52 | extern crate serde_derive; 53 | 54 | #[derive(Deserialize, Debug)] 55 | struct Environment { 56 | lang: String, 57 | } 58 | 59 | match envy::from_env::() { 60 | Ok(environment) => println!("Language code: {}", environment.lang), 61 | Err(e) => println!("Couldn't read LANG ({})", e), 62 | }; 63 | ``` 64 | 65 | `envy` normalizes variable names to lower case, so `LANG` becomes `lang` 66 | attribute of the struct. But because it's just `serde`, we can use 67 | [serde attributes](https://serde.rs/attributes.html#field-attributes) to 68 | control renames. Another thing worth noting is `serialize_with` attribute. 69 | For example if we had a comma-separated list in an environment variable, 70 | we could deserialize it to a `Vec` with something similar to the code in this 71 | [issue on GitHub](https://github.com/serde-rs/serde/issues/581). 72 | 73 | dotenv 74 | ------ 75 | 76 | Sometimes we don't want to export a lot of environment variables manually. 77 | We could write a shell script that wraps our application... or we can use 78 | `dotenv`. Dotenv is a convention to put environment variables in a `.env` 79 | file. There are libraries to read `.env` in 80 | [Ruby](https://github.com/bkeepers/dotenv), 81 | [Python](https://github.com/theskumar/python-dotenv), 82 | [PHP](https://github.com/vlucas/phpdotenv) and a few other languages, but 83 | here we're interested in the [`dotenv`](https://crates.io/crates/dotenv) crate. 84 | 85 | The `dotenv::dotenv()` function does one simple thing: it sets environment 86 | variables based on `.env` file contents. If we have a `.env` file like this: 87 | 88 | ```text 89 | EMAIL_FROM=root@localhost 90 | EMAIL_BACKEND=smtp 91 | ``` 92 | 93 | We can now read these from our Rust program like any other environment 94 | variables. 95 | 96 | ```rust 97 | extern crate dotenv; 98 | 99 | dotenv::dotenv().expect("Failed to read .env file"); 100 | println!("Email backend: {}", 101 | env::var("EMAIL_BACKEND").expect("EMAIL_BACKEND not found")); 102 | ``` 103 | 104 | Even better - we can combine `dotenv` with `envy` and read our configuration 105 | stored in the `.env` file into a Rust struct. 106 | 107 | ```rust 108 | #[derive(Deserialize, Debug)] 109 | struct MailerConfig { 110 | email_backend: String, 111 | email_from: String, 112 | } 113 | 114 | dotenv::dotenv().expect("Failed to read .env file"); 115 | match envy::from_env::() { 116 | Ok(config) => println!("{:?}", config), 117 | Err(e) => println!("Couldn't read mailer config ({})", e), 118 | }; 119 | ``` 120 | 121 | ```text 122 | $ cargo run 123 | MailerConfig { email_backend: "smtp", email_from: "root@localhost" } 124 | ``` 125 | 126 | Further reading 127 | --------------- 128 | 129 | - [What are PATH and other environment variables, and how can I set or use them?](http://superuser.com/questions/284342/what-are-path-and-other-environment-variables-and-how-can-i-set-or-use-them) 130 | - [The Twelve-factor app: store config in the environment](https://12factor.net/config) 131 | - [Implementing Deserialize with serde](https://serde.rs/impl-deserialize.html) 132 | -------------------------------------------------------------------------------- /book/vol2/day6.md: -------------------------------------------------------------------------------- 1 | # Day 6 - derive_builder 2 | 3 | Today our focus is the [derive_builder](https://crates.io/crates/derive_builder) 4 | crate. It provides a macro to automatically generate setter methods 5 | for struct fields. And since all setters return the struct itself, 6 | we can chain them in a so called *builder pattern*. 7 | 8 | Builder and fluent interfaces 9 | ----------------------------- 10 | 11 | [Builder pattern](https://en.wikipedia.org/wiki/Builder_pattern) (in the sense 12 | of the original Design Patterns book) is an object that knows how to construct 13 | other objects step by step. The builder approach aims to simplify object 14 | creation. Instead of a long argument list in the constructor (or a method 15 | like `new()` in Rust), we initialize the object by calling subsequent 16 | builder methods. 17 | 18 | Each builder method returns the builder itself. This allows for chaining 19 | the calls right after each other `just().like().this()`. For example 20 | [diesel](http://diesel.rs/) uses such method chaining to build database queries: 21 | 22 | ```rust 23 | let versions = Version::belonging_to(krate) 24 | .select(id) 25 | .order(num.desc()) 26 | .limit(5); 27 | ``` 28 | 29 | Fluent interfaces are useful in some contexts; they are not a silver bullet 30 | (nothing is). Marco Pivetta wrote a good critique in his blog post 31 | [Fluent interfaces are evil](https://ocramius.github.io/blog/fluent-interfaces-are-evil/). 32 | 33 | Game config example 34 | ------------------- 35 | 36 | Imagine we're writing a computer game. We need to store game 37 | configuration such as screen resolution, save directory location etc. A lot 38 | of modern games start via a separate launcher which tries to autodetect 39 | these values and presents initial config to the actual game. Let's see 40 | how we could use `derive_builder` in such a launcher. 41 | 42 | ```rust 43 | #[macro_use] 44 | extern crate custom_derive; 45 | #[macro_use] 46 | extern crate derive_builder; 47 | ``` 48 | 49 | [custom_derive](https://crates.io/crates/custom_derive) is a helper crate that 50 | allows deriving user-defined attributes. We'll see that later, but here's a 51 | regular derive: 52 | 53 | ```rust 54 | #[derive(Debug)] 55 | struct Resolution { 56 | width: u32, 57 | height: u32, 58 | } 59 | 60 | impl Default for Resolution { 61 | fn default() -> Resolution { 62 | Resolution { 63 | width: 1920, 64 | height: 1080, 65 | } 66 | } 67 | } 68 | ``` 69 | 70 | `Resolution` is just a pair of integers with a debugging representation 71 | derived for us by the Rust compiler. We could also derive `Default`, but this 72 | would use default values for both dimensions, giving us a 0px x 0px screen. Not 73 | really useful... 74 | 75 | ```rust 76 | custom_derive! { 77 | #[derive(Debug, Default, Builder)] 78 | struct GameConfig { 79 | resolution: Resolution, 80 | save_dir: Option, 81 | autosave: bool, 82 | fov: f32, 83 | render_distance: u32, 84 | } 85 | } 86 | ``` 87 | 88 | Here's the meat of the matter. `Debug` and `Default` are standard Rust traits 89 | that the compiler knows how to derive. `Builder` on the other hand is actually 90 | a `Builder!` macro, that `custom_derive!` knows how to call on our struct. 91 | 92 | So this is a plain Rust struct inside some macro. What does it buy us? 93 | 94 | ```rust 95 | let mut conf = GameConfig::default(); 96 | conf.save_dir("saves".to_string()).fov(70.0).render_distance(1000u32); 97 | println!("{:?}", conf); 98 | ``` 99 | 100 | ```text 101 | $ cargo run 102 | GameConfig { resolution: Resolution { width: 1920, height: 1080 }, save_dir: Some("saves"), autosave: false, fov: 70, render_distance: 1000 } 103 | ``` 104 | 105 | Note how the resolution and autosave flag are still correctly set from the 106 | defaults, while we use generated setters for the rest of settings. 107 | Another interesting thing happens when we set `save_dir`. The struct contains 108 | an `Option`, but we're passing a `String` into the setter. How does 109 | that even compile? 110 | 111 | Since [Rust 1.12](https://blog.rust-lang.org/2016/09/29/Rust-1.12.html), 112 | `Option` implements `From`. And the generated setter signature 113 | would look like: 114 | 115 | ```rust 116 | pub fn save_dir>(&mut self, value: VALUE) -> &mut Self 117 | ``` 118 | 119 | `From` and `Into` traits complement each other, hence everything typechecks. 120 | 121 | We can of course add `impl GameConfig` and implement our own methods - this is 122 | still a regular Rust struct. The only thing `derive_builder` does is adding 123 | public setter methods. 124 | 125 | (Non)-consuming builders 126 | ------------------------ 127 | 128 | There's a small gotcha with `derive_builder`. We can't initialize config like 129 | this: 130 | 131 | ```rust 132 | let mut conf = GameConfig::default().fov(0.0); 133 | ``` 134 | 135 | This will fail to compile with an error like below: 136 | 137 | ```text 138 | error: borrowed value does not live long enough 139 | --> src\day6.rs:34:50 140 | | 141 | 34 | let mut conf = GameConfig::default().fov(0.0); 142 | | --------------------- ^ temporary value dropped here while still borrowed 143 | | | 144 | | temporary value created here 145 | ... 146 | 37 | } 147 | | - temporary value needs to live until here 148 | | 149 | = note: consider using a `let` binding to increase its lifetime 150 | ``` 151 | 152 | `derive_builder` prefers a so called *non-consuming* builder approach, as 153 | described in [The builder pattern](https://aturon.github.io/ownership/builders.html). 154 | See [this issue](https://github.com/colin-kiegel/rust-derive-builder/issues/2) 155 | for the rationale behind this choice. 156 | 157 | Further reading 158 | --------------- 159 | 160 | - [The builder pattern](https://aturon.github.io/ownership/builders.html) 161 | - [FluentInterface by Martin Fowler](http://martinfowler.com/bliki/FluentInterface.html) 162 | - [Fluent interfaces](http://www.erikschierboom.com/2014/10/08/fluent-interfaces/) (in C#) 163 | - [Fluent interfaces are evil](https://ocramius.github.io/blog/fluent-interfaces-are-evil/) 164 | - [smelter](https://crates.io/crates/smelter) - a similar crate for deriving builders 165 | -------------------------------------------------------------------------------- /book/vol2/day7.md: -------------------------------------------------------------------------------- 1 | # Day 7 - static initialization 2 | 3 | [Static variables](https://en.wikipedia.org/wiki/Static_variable) are available 4 | throughout the entire life of a program. They are allocated in a block of 5 | memory known at compile time. Due to that, they tend to represent global 6 | state that the program can access. 7 | It's getting especially tricky if one static variable depends on another. 8 | Some language communities even talk about 9 | [static initialization order fiasco](https://isocpp.org/wiki/faq/ctors#static-init-order) 10 | (looking at you, C++). Other, like C, allow static intialization only with 11 | constant literals/expressions. Rust 12 | [belongs to this group](https://doc.rust-lang.org/beta/book/const-and-static.html) 13 | as well. But there are alternatives... 14 | 15 | Looking up colors by name 16 | ------------------------- 17 | 18 | Suppose we're building a web browser engine. Among thousands of things to take 19 | care of, we should be able to render colorful text. `

` 20 | should look like a paragraph set in a blue font. But `blue` is a human-readable 21 | name for a color; computers like numbers. Let's translate it with the help 22 | of a `Color` struct: 23 | 24 | ```rust 25 | #[derive(Clone, Debug)] 26 | pub struct Color { 27 | r: u8, 28 | g: u8, 29 | b: u8, 30 | } 31 | ``` 32 | 33 | We cannot create a static `HashMap` and initialize it with a mapping 34 | from color names to `Color`s. So let's use pattern matching to find 35 | color by name: 36 | 37 | ```rust 38 | pub fn find_color(name: &str) -> Option { 39 | match name.to_lowercase().as_str() { 40 | "amber" => Some(Color { r: 255, g: 191, b: 0 }), 41 | // hundreds of other names... 42 | "zinnwaldite brown" => Some(Color { r: 44, g: 22, b: 8 }), 43 | _ => None, 44 | } 45 | } 46 | ``` 47 | 48 | The downside is that matching string slices will probably generate a linear 49 | search. So the more colors we have, the slower `find_color` will be. 50 | 51 | Did I just say we cannot have a static `HashMap`? 52 | 53 | > *puts on Morpheus sunglasses* 54 | 55 | What if I told you... 56 | 57 | `lazy_static!` 58 | -------------- 59 | 60 | [`lazy_static`](https://crates.io/crates/lazy_static/) is a crate that 61 | enables `static` variables that are initialized in a non-trivial way. 62 | For example a precomputed regular epression such as 63 | [the ones used in `docopt`](https://github.com/docopt/docopt.rs/blob/717c26c1924d2c95fac48814c96ac6979fe2f20d/src/parse.rs#L182), 64 | or a static `HashMap`! 65 | 66 | ```rust 67 | #[macro_use] 68 | extern crate lazy_static; 69 | 70 | use std::collections::HashMap; 71 | 72 | lazy_static! { 73 | static ref COLORS_MAP: HashMap<&'static str, Color> = { 74 | let mut map = HashMap::new(); 75 | map.insert("amber", Color { r: 255, g: 191, b: 0 }); 76 | // ... 77 | map.insert("zinnwaldite brown", Color { r: 44, g: 22, b: 8 }); 78 | map 79 | }; 80 | } 81 | 82 | pub fn find_color_lazy_static(name: &str) -> Option { 83 | COLORS_MAP.get(name.to_lowercase().as_str()).cloned() 84 | } 85 | ``` 86 | 87 | `COLORS_MAP` will be evaluated on first access. We can now safely treat 88 | it as if it was a regular static variable. 89 | 90 | `phf` 91 | ----- 92 | 93 | `HashMap` uses a *somewhat slow hash algorithm* (quoting the documentation) 94 | to avoid DoS attacks. With large enough maps there's also a possibility of 95 | collisions. 96 | 97 | On the other hand, [`phf`](https://crates.io/crates/phf) uses perfect hashing 98 | (hashing that guarantees no collisions) to build compile-time maps. 99 | This way we can have efficient constant-time lookups at runtime. 100 | 101 | ```rust 102 | #![feature(plugin)] 103 | #![plugin(phf_macros)] 104 | 105 | #[macro_use] 106 | extern crate phf; 107 | 108 | static COLORS: phf::Map<&'static str, Color> = phf_map! { 109 | "amber" => Color { r: 255, g: 191, b: 0 }, 110 | // ... 111 | "zinnwaldite brown" => Color { r: 44, g: 22, b: 8 }, 112 | }; 113 | 114 | pub fn find_color_phf(name: &str) -> Option { 115 | COLORS.get(name.to_lowercase().as_str()).cloned() 116 | } 117 | ``` 118 | 119 | Benchmarks! 120 | ----------- 121 | 122 | So what's the difference in speed between these approaches? 123 | 124 | ```rust 125 | #[cfg(test)] 126 | mod tests { 127 | use super::*; 128 | use test::Bencher; 129 | 130 | #[bench] 131 | fn bench_match_lookup(b: &mut Bencher) { 132 | b.iter(|| find_color("White")) 133 | } 134 | 135 | #[bench] 136 | fn bench_lazy_static_map(b: &mut Bencher) { 137 | b.iter(|| find_color_lazy_static("White")) 138 | } 139 | 140 | #[bench] 141 | fn bench_phf_map(b: &mut Bencher) { 142 | b.iter(|| find_color_phf("White")) 143 | } 144 | } 145 | ``` 146 | 147 | ```text 148 | $ cargo bench 149 | running 3 tests 150 | test tests::bench_lazy_static_map ... bench: 367 ns/iter (+/- 20) 151 | test tests::bench_match_lookup ... bench: 3,948 ns/iter (+/- 460) 152 | test tests::bench_phf_map ... bench: 350 ns/iter (+/- 33) 153 | ``` 154 | 155 | Linear search at runtime in the `match` statement is the slowest, as expected. 156 | Both static `HashMap` and `phf::Map` are about an order of magnitude faster, 157 | with the latter leading by a small amount. I would personally prefer `phf` 158 | for lookups like this, as it is the intended use for static compile-time 159 | maps. `lazy_static` is more general in its intent and initializing maps 160 | is just one of its many potential uses. 161 | 162 | Further reading 163 | --------------- 164 | 165 | - [Static initializers will murder your family](https://meowni.ca/posts/static-initializers/) (in C++, fortunately not Rust) 166 | - [Perfect hashing functions](https://en.wikipedia.org/wiki/Perfect_hash_function) 167 | - [Match statement efficiency?](https://users.rust-lang.org/t/match-statement-efficiency/4488/1) 168 | -------------------------------------------------------------------------------- /book/vol2/day9.md: -------------------------------------------------------------------------------- 1 | # Day 9 - winreg 2 | 3 | Fact: I'm writing these articles and examples on a Windows machine and 4 | so far everything compiles and works as expected. Just so you know, Rust 5 | [supports Windows in the top tier](https://forge.rust-lang.org/platform-support.html). 6 | I'm mentioning it here since a few people I talked to assumed Windows support 7 | was sort of secondary, bolted-on later. This is **not** the case. 8 | 9 | The library ecosystem also supports different operating systems fairly well. 10 | There are even cross-platform crates for stuff usually associated with Linux, 11 | such as [curses](https://github.com/ihalila/pancurses) or 12 | [coreutils](https://github.com/uutils/coreutils). 13 | However, some crates support only Linux or Windows by design. One of them is 14 | [`winreg`](https://crates.io/crates/winreg) - a Rust API to access 15 | Windows Registry. 16 | 17 | Read a single value 18 | ------------------- 19 | 20 | The Registry is a tree of keys, similar to a filesystem with folders. 21 | Keys may contain other keys or values. 22 | 23 | ```rust 24 | extern crate winreg; 25 | 26 | use winreg::enums::{HKEY_LOCAL_MACHINE, KEY_READ}; 27 | 28 | let hklm = winreg::RegKey::predef(HKEY_LOCAL_MACHINE); 29 | let subkey = 30 | hklm.open_subkey_with_flags(r#"SOFTWARE\Microsoft\Windows NT\CurrentVersion"#, KEY_READ) 31 | .expect("Failed to open subkey"); 32 | let product_name: String = subkey.get_value("ProductName") 33 | .expect("Failed to read product name"); 34 | println!("Windows version: {}", product_name); 35 | ``` 36 | 37 | ```text 38 | $ cargo run 39 | Windows version: Windows 7 Home Premium 40 | ``` 41 | 42 | `HKEY_LOCAL_MACHINE` is one of the predefined root keys that we use as a 43 | starting point to find `SOFTWARE\Microsoft\Windows NT\CurrentVersion`. 44 | (We're using raw string literals here so that we don't have to escape 45 | backslashes.) This key contains a whole lot of subkeys, but also several values 46 | such as OS version, Service Pack version, build numer etc. 47 | 48 | List installed programs 49 | ----------------------- 50 | 51 | ```rust 52 | let subkey = 53 | hklm.open_subkey_with_flags(r#"Software\Microsoft\Windows\CurrentVersion\Uninstall"#, 54 | KEY_READ) 55 | .expect("Failed to open subkey"); 56 | for key in subkey.enum_keys() { 57 | let _ = key.and_then(|key| subkey.open_subkey_with_flags(key, KEY_READ)) 58 | .and_then(|program_key| program_key.get_value("DisplayName")) 59 | .and_then(|name: String| { 60 | println!("{}", name); 61 | Ok(()) 62 | }); 63 | } 64 | ``` 65 | 66 | ```text 67 | $ cargo run 68 | Git version 2.9.0 69 | HashTab 4.0.0.2 70 | Microsoft Security Essentials 71 | Intel PROSet Wireless 72 | Stellarium 0.12.0 73 | ... and a lot more 74 | ``` 75 | 76 | Note: this is an incomplete list. See this MS technet answer for a **much more** 77 | detailed approach: 78 | [Where does Add/Remove Programs get its information from in the registry](https://social.technet.microsoft.com/Forums/windows/en-US/d913471a-d7fb-448d-869b-da9025dcc943/where-does-addremove-programs-get-its-information-from-in-the-registry?forum=w7itprogeneral) 79 | 80 | Tweaking the system 81 | ------------------- 82 | 83 | **Note**: you'll probably want to 84 | [back up your Registry](https://support.microsoft.com/en-us/kb/322756) before 85 | making changes to it. 86 | 87 | Windows 7 introduced thumbnail previews for taskbar icons, showing the contents 88 | of corresponding windows. In the screenshot below I moved the mouse cursor over 89 | Firefox icon and after a small delay a thumbnail appeared. 90 | 91 | ![](http://wstaw.org/m/2016/12/07/thumbnails.png) 92 | 93 | Yes, there is a small delay. But we can tweak it using the Registry. 94 | [Here's a short tutorial](http://www.askvg.com/how-to-adjust-taskbar-thumbnail-delay-time-in-windows-7/) 95 | on how to do it by hand, but we're going to use Rust and `winreg`. 96 | 97 | ```rust 98 | let delay = 100u32; // in milliseconds 99 | let hkcu = winreg::RegKey::predef(HKEY_CURRENT_USER); 100 | let subkey = 101 | hkcu.open_subkey_with_flags(r#"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"#, 102 | KEY_WRITE) 103 | .expect("Failed to open subkey"); 104 | subkey.set_value("ExtendedUIHoverTime", &delay).expect("Failed to change thumbnail timeout"); 105 | ``` 106 | 107 | If we navigate using `regedit.exe` to the key mentioned above, we'll notice 108 | there's now an `ExtendedUIHoverTime` value among others. That's the new timeout! 109 | Restart `explorer.exe` or log out and back in to see the change in delay. 110 | 111 | Further reading 112 | --------------- 113 | 114 | - [Installing Rust on Windows](https://facility9.com/2016/03/installing-rust-on-windows/) - includes setting up OpenSSL and a C/C++ compiler 115 | - [Useful Windows Registry Keys](http://www.jasinskionline.com/TechnicalWiki/Useful-Windows-Registry-Keys.ashx) 116 | 117 | -------------------------------------------------------------------------------- /deploy-book.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | rev=$(git rev-parse --short HEAD) 6 | 7 | cd _book 8 | 9 | git init 10 | git config user.name "Zbigniew Siciarz" 11 | git config user.email "zbigniew@siciarz.net" 12 | git remote add upstream "https://$GH_TOKEN@github.com/$TRAVIS_REPO_SLUG.git" 13 | git fetch upstream 14 | git reset upstream/gh-pages 15 | 16 | touch . 17 | 18 | git add -A . 19 | git commit -m "update book at ${rev}" 20 | git push upstream HEAD:gh-pages 21 | -------------------------------------------------------------------------------- /vol1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "24daysofrust" 3 | version = "0.0.1" 4 | authors = ["Zbigniew Siciarz "] 5 | 6 | [[bin]] 7 | name = "day2" 8 | 9 | [[bin]] 10 | name = "day3" 11 | 12 | #[[bin]] 13 | #name = "day4" 14 | 15 | [[bin]] 16 | name = "day5" 17 | 18 | [[bin]] 19 | name = "day6" 20 | 21 | [[bin]] 22 | name = "day7" 23 | 24 | [[bin]] 25 | name = "day9" 26 | 27 | [[bin]] 28 | name = "day10" 29 | 30 | [[bin]] 31 | name = "day11" 32 | 33 | [[bin]] 34 | name = "day12" 35 | 36 | [[bin]] 37 | name = "day13" 38 | 39 | [[bin]] 40 | name = "day14" 41 | 42 | [[bin]] 43 | name = "day15" 44 | 45 | [[bin]] 46 | name = "day17" 47 | 48 | [[bin]] 49 | name = "day18" 50 | 51 | [[bin]] 52 | name = "day20" 53 | 54 | [[bin]] 55 | name = "day21" 56 | 57 | [dependencies] 58 | anymap = "0.12.1" 59 | csv = "0.15.0" 60 | docopt = "0.8.1" 61 | #docopt_macros = "0.7.0" 62 | hyper = { version = "0.10.13", default-features = false } 63 | image = "0.17.0" 64 | itertools = "0.7.2" 65 | json_macros = { git = "https://github.com/zsiciarz/json_macros.git" } 66 | libc = "0.2.24" 67 | nalgebra = "0.13.1" 68 | num = "0.1.40" 69 | postgres = { version = "0.15.1", features = ["with-rustc-serialize", "with-time"] } 70 | postgres_array = "0.9.0" 71 | #postgres_range = "0.8.2" 72 | primal = "0.2.3" 73 | rand = "0.3.18" 74 | redis = "0.8.0" 75 | rust-crypto = "0.2.36" 76 | rustc-serialize = "0.3.18" 77 | time = "0.1.38" 78 | tau = "1.0.4" 79 | url = "1.6.0" 80 | uuid = { version = "0.5.1", features = ["v4"] } 81 | 82 | [target.'cfg(unix)'.dependencies] 83 | fuse = "0.3.1" 84 | #postgres_macros = "0.1.13" 85 | zmq = "0.8.2" 86 | 87 | #[replace] 88 | #"docopt_macros:0.7.0" = { git = "https://github.com/zsiciarz/docopt.rs.git" } 89 | -------------------------------------------------------------------------------- /vol1/data/in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsiciarz/24daysofrust/52eeea16bca1a89f599790318c569cef7b2ea712/vol1/data/in.png -------------------------------------------------------------------------------- /vol1/src/bin/day10.rs: -------------------------------------------------------------------------------- 1 | extern crate num; 2 | extern crate tau; 3 | 4 | use num::complex::{Complex, Complex64}; 5 | 6 | fn main() { 7 | println!("24 days of Rust - tau (day 10)"); 8 | println!("τ = {}", tau::TAU); 9 | let radius: f64 = 15.0; 10 | println!("circle circumference = τ * r = {}", tau::TAU * radius); 11 | let c: Complex64 = Complex::from_polar(&1.0, &tau::TAU); 12 | println!("Euler's identity: exp(i * τ) = {}", c); 13 | println!( 14 | "Trigonometry: sin(τ) = {}, cos(τ) = {}", 15 | tau::TAU.sin(), 16 | tau::TAU.cos() 17 | ); 18 | println!("That other constant = {}", tau::TAU / 2.0); 19 | } 20 | -------------------------------------------------------------------------------- /vol1/src/bin/day11.rs: -------------------------------------------------------------------------------- 1 | // #![feature(plugin)] 2 | // #![cfg_attr(target_family="unix", plugin(postgres_macros))] 3 | 4 | 5 | extern crate rustc_serialize; 6 | extern crate time; 7 | extern crate postgres; 8 | extern crate postgres_array; 9 | // extern crate postgres_range; 10 | 11 | use postgres::{Connection, TlsMode}; 12 | use postgres::types::FromSql; 13 | use postgres::Result as PgResult; 14 | use postgres_array::Array; 15 | // use postgres_range::Range; 16 | 17 | use rustc_serialize::json::Json; 18 | 19 | use time::Timespec; 20 | 21 | fn get_single_value(conn: &Connection, query: &str) -> PgResult 22 | where 23 | T: FromSql, 24 | { 25 | println!("Executing query: {}", query); 26 | let stmt = conn.prepare(query)?; 27 | let rows = stmt.query(&[])?; 28 | let row = rows.iter().next().unwrap(); 29 | row.get_opt(0).unwrap() 30 | } 31 | 32 | #[cfg(target_family = "unix")] 33 | fn sql_macro() { 34 | //let query = sql!("select '{4, 5, 6}'::int[]"); 35 | //println!("{:?}", query); 36 | } 37 | 38 | #[cfg(not(target_family = "unix"))] 39 | fn sql_macro() { 40 | println!("TODO"); 41 | } 42 | 43 | fn main() { 44 | println!("24 days of Rust - postgres (day 11)"); 45 | let dsn = "postgresql://rust:rust@localhost/rust"; 46 | let conn = match Connection::connect(dsn, TlsMode::None) { 47 | Ok(conn) => conn, 48 | Err(e) => { 49 | println!("Connection error: {:?}", e); 50 | return; 51 | } 52 | }; 53 | conn.execute( 54 | "create table if not exists blog ( 55 | id serial primary key, 56 | title \ 57 | varchar(255), 58 | body text)", 59 | &[], 60 | ).expect("Table creation failed"); 61 | let stmt = match conn.prepare("insert into blog (title, body) values ($1, $2)") { 62 | Ok(stmt) => stmt, 63 | Err(e) => { 64 | println!("Preparing query failed: {:?}", e); 65 | return; 66 | } 67 | }; 68 | for i in 1..5 { 69 | let title = format!("Blogpost number {}", i); 70 | let text = format!("Content of the blogpost #{}", i); 71 | stmt.execute(&[&title, &text]).expect( 72 | "Inserting blogposts failed", 73 | ); 74 | } 75 | let stmt = match conn.prepare("select id, title, body from blog where id < $1") { 76 | Ok(stmt) => stmt, 77 | Err(e) => { 78 | println!("Preparing query failed: {:?}", e); 79 | return; 80 | } 81 | }; 82 | let max_id: i32 = 3; 83 | let rows = stmt.query(&[&max_id]).expect("Selecting blogposts failed"); 84 | for row in rows.iter() { 85 | let id: i32 = row.get("id"); 86 | let title: String = row.get("title"); 87 | println!("ID={}, title={}", id, title); 88 | } 89 | println!("{:?}", get_single_value::(&conn, "select 1=1")); 90 | println!("{:?}", get_single_value::(&conn, "select 1=1")); 91 | 92 | type IntArray = Array>; 93 | let arr = get_single_value::(&conn, "select '{4, 5, 6}'::int[]"); 94 | println!( 95 | "{:?}", 96 | arr.map(|arr| arr.iter().filter_map(|x| *x).collect::>()) 97 | ); 98 | 99 | let json = get_single_value::(&conn, "select '{\"foo\": \"bar\", \"answer\": 42}'::json"); 100 | println!("{:?}", json); 101 | 102 | // let range = get_single_value::>(&conn, "select '[10, 20)'::int4range"); 103 | // println!("{:?}", range); 104 | // let ts_range = 105 | // get_single_value::>(&conn, "select '[2015-01-01, 2015-12-31]'::tsrange"); 106 | // println!("{:?}", ts_range); 107 | 108 | sql_macro(); 109 | } 110 | -------------------------------------------------------------------------------- /vol1/src/bin/day12.rs: -------------------------------------------------------------------------------- 1 | extern crate image; 2 | extern crate rand; 3 | 4 | use image::{FilterType, GenericImage, Pixel}; 5 | 6 | use rand::distributions::{Normal, IndependentSample}; 7 | use std::fs::File; 8 | 9 | fn main() { 10 | println!("24 days of Rust - image (day 12)"); 11 | let img = image::open("data/in.png").expect("Opening image failed"); 12 | let filtered = img.fliph(); 13 | let mut out = File::create("out.png").unwrap(); 14 | filtered.save(&mut out, image::PNG).expect( 15 | "Saving image failed", 16 | ); 17 | 18 | let kernel = [-1.0f32, -1.0, -1.0, -1.0, 8.0, -1.0, -1.0, -1.0, -1.0]; 19 | let filtered = img.filter3x3(&kernel); 20 | let mut out = File::create("out_blur.png").unwrap(); 21 | filtered.save(&mut out, image::PNG).expect( 22 | "Saving image failed", 23 | ); 24 | 25 | let (width, height) = img.dimensions(); 26 | let mut rng = rand::thread_rng(); 27 | let normal = Normal::new(15.0, 15.0); 28 | let mut noisy = img.brighten(-25); 29 | for x in 0..(width) { 30 | for y in 0..(height) { 31 | let offset = normal.ind_sample(&mut rng) as u8; 32 | let px = img.get_pixel(x, y).map(|v| if v <= 255 - offset { 33 | v + offset 34 | } else { 35 | 255 36 | }); 37 | noisy.put_pixel(x, y, px); 38 | } 39 | } 40 | let mut out = File::create("out_noisy.png").unwrap(); 41 | noisy.save(&mut out, image::PNG).expect( 42 | "Saving image failed", 43 | ); 44 | 45 | let thumbnail = img.resize(120, 120, FilterType::Lanczos3); 46 | let mut out = File::create("out_thumb.png").unwrap(); 47 | thumbnail.save(&mut out, image::PNG).expect( 48 | "Saving image failed", 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /vol1/src/bin/day13.rs: -------------------------------------------------------------------------------- 1 | extern crate uuid; 2 | 3 | use uuid::Uuid; 4 | 5 | fn main() { 6 | println!("24 days of Rust - uuid (day 13)"); 7 | for _ in 0..10 { 8 | println!("{}", Uuid::new_v4().hyphenated().to_string()); 9 | } 10 | println!( 11 | "{:?}", 12 | Uuid::parse_str("d27cdb6e-ae6d-11cf-96b8-44455354000") 13 | ); 14 | println!( 15 | "{:?}", 16 | Uuid::parse_str("x27cdb6e-ae6d-11cf-96b8-444553540000") 17 | ); 18 | println!( 19 | "{:?}", 20 | Uuid::parse_str("d27cdb6-eae6d-11cf-96b8-444553540000") 21 | ); 22 | println!( 23 | "{:?}", 24 | Uuid::parse_str("d27cdb6e-ae6d-11cf-96b8-444553540000") 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /vol1/src/bin/day14.rs: -------------------------------------------------------------------------------- 1 | extern crate image; 2 | extern crate nalgebra; 3 | 4 | use std::f64::consts::{PI, FRAC_PI_2}; 5 | use std::fs::File; 6 | use std::path::Path; 7 | use image::{GenericImage, Pixel, Rgba}; 8 | use nalgebra::{DVector, Matrix2, Point2, Rotation2, Vector2, Vector3}; 9 | 10 | fn draw(v: &DVector, path: &Path) { 11 | let width = v.len() as u32; 12 | let height = 128u32; 13 | let white: Rgba = Pixel::from_channels(255, 255, 255, 255); 14 | let buffer = image::ImageBuffer::from_pixel(width, height, white); 15 | let mut img = image::DynamicImage::ImageRgba8(buffer); 16 | let red = Pixel::from_channels(255, 0, 0, 255); 17 | for i in 0u32..(width) { 18 | let half = (height / 2) as f64; 19 | let y = half * (1.0 + v[i as usize]); 20 | let y = nalgebra::clamp(y as u32, 1u32, height); 21 | img.put_pixel(i, height - y, red); 22 | } 23 | let mut out = File::create(path).unwrap(); 24 | let _ = img.save(&mut out, image::PNG); 25 | } 26 | 27 | fn main() { 28 | println!("24 days of Rust - nalgebra (day 14)"); 29 | let v = Vector2::new(0.0f64, 1.0); 30 | // 90 degrees clockwise 31 | // 0, 1 32 | // -1, 0 33 | let rot = Matrix2::new(0.0f64, -1.0, 1.0, 0.0); 34 | println!("{}", rot * v); 35 | let angle = FRAC_PI_2; 36 | let rot = Rotation2::new(angle); 37 | println!("{}", rot * v); 38 | let point = Point2::new(4.0f64, 4.0); 39 | println!("Translate from {} to {}", point, point + v); 40 | 41 | let v1 = Vector3::new(2.0f64, 2.0, 0.0); 42 | let v2 = Vector3::new(2.0f64, -2.0, 0.0); 43 | println!("Dot product: {}", v1.dot(&v2)); 44 | 45 | println!("{}", v1.cross(&v2)); 46 | println!("{}", v2.cross(&v1)); 47 | 48 | const SIZE: usize = 512; 49 | let sine = DVector::from_fn(SIZE, |i: usize, _| { 50 | let t = i as f64 / 16.0f64; 51 | t.sin() 52 | }); 53 | draw(&sine, Path::new("out_sine.png")); 54 | 55 | let window = DVector::from_fn(SIZE, |i: usize, _| { 56 | 0.54f64 - 0.46 * (PI * 2.0 * (i as f64) / (SIZE - 1) as f64).cos() 57 | }); 58 | draw(&window, Path::new("out_window.png")); 59 | 60 | draw(&sine.component_mul(&window), Path::new("out_windowed.png")); 61 | } 62 | -------------------------------------------------------------------------------- /vol1/src/bin/day15.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_family = "unix")] 2 | extern crate fuse; 3 | extern crate time; 4 | extern crate libc; 5 | extern crate rustc_serialize; 6 | 7 | #[cfg(target_family = "unix")] 8 | use std::collections::BTreeMap; 9 | #[cfg(target_family = "unix")] 10 | use std::env; 11 | #[cfg(target_family = "unix")] 12 | use std::ffi::OsStr; 13 | #[cfg(target_family = "unix")] 14 | use libc::ENOENT; 15 | #[cfg(target_family = "unix")] 16 | use time::Timespec; 17 | #[cfg(target_family = "unix")] 18 | use fuse::{FileAttr, FileType, Filesystem, Request, ReplyAttr, ReplyData, ReplyEntry, 19 | ReplyDirectory}; 20 | #[cfg(target_family = "unix")] 21 | use rustc_serialize::json; 22 | 23 | #[cfg(target_family = "unix")] 24 | struct JsonFilesystem { 25 | tree: json::Object, 26 | attrs: BTreeMap, 27 | inodes: BTreeMap, 28 | } 29 | 30 | #[cfg(target_family = "unix")] 31 | impl JsonFilesystem { 32 | fn new(tree: &json::Object) -> JsonFilesystem { 33 | let mut attrs = BTreeMap::new(); 34 | let mut inodes = BTreeMap::new(); 35 | let ts = time::now().to_timespec(); 36 | let attr = FileAttr { 37 | ino: 1, 38 | size: 0, 39 | blocks: 0, 40 | atime: ts, 41 | mtime: ts, 42 | ctime: ts, 43 | crtime: ts, 44 | kind: FileType::Directory, 45 | perm: 0o755, 46 | nlink: 0, 47 | uid: 0, 48 | gid: 0, 49 | rdev: 0, 50 | flags: 0, 51 | }; 52 | attrs.insert(1, attr); 53 | inodes.insert("/".to_string(), 1); 54 | for (i, (key, value)) in tree.iter().enumerate() { 55 | let attr = FileAttr { 56 | ino: i as u64 + 2, 57 | size: value.pretty().to_string().len() as u64, 58 | blocks: 0, 59 | atime: ts, 60 | mtime: ts, 61 | ctime: ts, 62 | crtime: ts, 63 | kind: FileType::RegularFile, 64 | perm: 0o644, 65 | nlink: 0, 66 | uid: 0, 67 | gid: 0, 68 | rdev: 0, 69 | flags: 0, 70 | }; 71 | attrs.insert(attr.ino, attr); 72 | inodes.insert(key.clone(), attr.ino); 73 | } 74 | JsonFilesystem { 75 | tree: tree.clone(), 76 | attrs: attrs, 77 | inodes: inodes, 78 | } 79 | } 80 | } 81 | 82 | #[cfg(target_family = "unix")] 83 | impl Filesystem for JsonFilesystem { 84 | fn getattr(&mut self, _req: &Request, ino: u64, reply: ReplyAttr) { 85 | println!("getattr(ino={})", ino); 86 | match self.attrs.get(&ino) { 87 | Some(attr) => { 88 | let ttl = Timespec::new(1, 0); 89 | reply.attr(&ttl, attr); 90 | } 91 | None => reply.error(ENOENT), 92 | }; 93 | } 94 | 95 | fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) { 96 | println!("lookup(parent={}, name={})", parent, name.to_str().unwrap()); 97 | let inode = match self.inodes.get(name.to_str().unwrap()) { 98 | Some(inode) => inode, 99 | None => { 100 | reply.error(ENOENT); 101 | return; 102 | } 103 | }; 104 | match self.attrs.get(inode) { 105 | Some(attr) => { 106 | let ttl = Timespec::new(1, 0); 107 | reply.entry(&ttl, attr, 0); 108 | } 109 | None => reply.error(ENOENT), 110 | }; 111 | } 112 | 113 | fn read( 114 | &mut self, 115 | _req: &Request, 116 | ino: u64, 117 | fh: u64, 118 | offset: i64, 119 | size: u32, 120 | reply: ReplyData, 121 | ) { 122 | println!( 123 | "read(ino={}, fh={}, offset={}, size={})", 124 | ino, 125 | fh, 126 | offset, 127 | size 128 | ); 129 | for (key, &inode) in &self.inodes { 130 | if inode == ino { 131 | let value = &self.tree[key]; 132 | reply.data(value.pretty().to_string().as_bytes()); 133 | return; 134 | } 135 | } 136 | reply.error(ENOENT); 137 | } 138 | 139 | fn readdir( 140 | &mut self, 141 | _req: &Request, 142 | ino: u64, 143 | fh: u64, 144 | offset: i64, 145 | mut reply: ReplyDirectory, 146 | ) { 147 | println!("readdir(ino={}, fh={}, offset={})", ino, fh, offset); 148 | if ino == 1 { 149 | if offset == 0 { 150 | reply.add(1, 0, FileType::Directory, "."); 151 | reply.add(1, 1, FileType::Directory, ".."); 152 | for (key, &inode) in &self.inodes { 153 | if inode == 1 { 154 | continue; 155 | } 156 | let offset = inode as i64; // hack 157 | println!("\tkey={}, inode={}, offset={}", key, inode, offset); 158 | reply.add(inode, offset, FileType::RegularFile, key); 159 | } 160 | } 161 | reply.ok(); 162 | } else { 163 | reply.error(ENOENT); 164 | } 165 | } 166 | } 167 | 168 | #[cfg(target_family = "windows")] 169 | fn main() { 170 | println!("24 days of Rust - fuse (days 15 & 16)"); 171 | println!("This example does not work on Windows :("); 172 | } 173 | 174 | #[cfg(target_family = "unix")] 175 | fn main() { 176 | println!("24 days of Rust - fuse (days 15 & 16)"); 177 | let data = json::Json::from_str("{\"foo\": \"bar\", \"answer\": 42}").unwrap(); 178 | let tree = data.as_object().unwrap(); 179 | let fs = JsonFilesystem::new(tree); 180 | let mountpoint = match env::args().nth(1) { 181 | Some(path) => path, 182 | None => { 183 | println!("Usage: {} ", env::args().nth(0).unwrap()); 184 | return; 185 | } 186 | }; 187 | fuse::mount(fs, &mountpoint, &[]).expect("Couldn't mount filesystem"); 188 | } 189 | -------------------------------------------------------------------------------- /vol1/src/bin/day17.rs: -------------------------------------------------------------------------------- 1 | extern crate image; 2 | extern crate nalgebra; 3 | extern crate rand; 4 | 5 | use image::Pixel; 6 | use nalgebra::DMatrix; 7 | 8 | use std::fs::File; 9 | use std::path::Path; 10 | 11 | fn main() { 12 | println!("24 days of Rust - patterns (day 17)"); 13 | let v = (0..10).map(|x| x * 3).collect::>(); 14 | println!("{:?}", v); 15 | let v = (0..10).map(|_| rand::random::()).collect::>(); 16 | println!("{:?}", v); 17 | let mat: DMatrix = DMatrix::from_fn(7, 7, |i, j| if j <= i { 1 } else { 0 }); 18 | println!("{:?}", mat); 19 | let buffer = image::ImageBuffer::from_fn(512u32, 512u32, |x: u32, y: u32| { 20 | Pixel::from_channels((x * y % 256) as u8, (y % 256) as u8, (x % 256) as u8, 255) 21 | }); 22 | let img = image::DynamicImage::ImageRgba8(buffer); 23 | let mut out = File::create(&Path::new("out_pattern.png")).unwrap(); 24 | let _ = img.save(&mut out, image::PNG); 25 | } 26 | -------------------------------------------------------------------------------- /vol1/src/bin/day18.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | extern crate redis; 3 | 4 | use redis::{Client, Commands, Connection, RedisResult}; 5 | use std::collections::HashSet; 6 | 7 | type UserId = u64; 8 | 9 | fn add_friend(conn: &Connection, my_id: UserId, their_id: UserId) -> RedisResult<()> { 10 | let my_key = format!("friends:{}", my_id); 11 | let their_key = format!("friends:{}", their_id); 12 | let _: () = conn.sadd(my_key, their_id)?; 13 | let _: () = conn.sadd(their_key, my_id)?; 14 | Ok(()) 15 | } 16 | 17 | fn friends_in_common( 18 | conn: &Connection, 19 | my_id: UserId, 20 | their_id: UserId, 21 | ) -> RedisResult> { 22 | let my_key = format!("friends:{}", my_id); 23 | let their_key = format!("friends:{}", their_id); 24 | conn.sinter((my_key, their_key)) 25 | } 26 | 27 | fn add_score(conn: &Connection, username: &str, score: u32) -> RedisResult<()> { 28 | conn.zadd("leaderboard", username, score) 29 | } 30 | 31 | type Leaderboard = Vec<(String, u32)>; 32 | 33 | fn show_leaderboard(conn: &Connection, n: isize) { 34 | let result: RedisResult = conn.zrevrange_withscores("leaderboard", 0, n - 1); 35 | match result { 36 | Ok(board) => { 37 | println!("----==== Top {} players ====----", n); 38 | for (i, (username, score)) in board.into_iter().enumerate() { 39 | println!("{:<5} {:^20} {:>4}", i + 1, username, score); 40 | } 41 | } 42 | Err(_) => println!("Failed to fetch leaderboard."), 43 | } 44 | } 45 | 46 | fn main() { 47 | println!("24 days of Rust - redis (day 18)"); 48 | let client = Client::open("redis://127.0.0.1/").unwrap(); 49 | let conn = client.get_connection().unwrap(); 50 | let _: () = conn.set("answer", 42).unwrap(); 51 | let answer: i32 = conn.get("answer").unwrap(); 52 | println!("Answer: {}", answer); 53 | 54 | for i in 1..10u64 { 55 | add_friend(&conn, i, i + 2).expect("Friendship failed :("); 56 | } 57 | println!( 58 | "You have {} friends in common.", 59 | friends_in_common(&conn, 2, 3).map(|s| s.len()).unwrap_or(0) 60 | ); 61 | 62 | let players = vec!["raynor", "kerrigan", "mengsk", "zasz", "tassadar"]; 63 | for player in &players { 64 | let score = rand::random::() % 1000; 65 | add_score(&conn, *player, score).expect("Nuclear launch detected"); 66 | } 67 | show_leaderboard(&conn, 3); 68 | } 69 | -------------------------------------------------------------------------------- /vol1/src/bin/day2.rs: -------------------------------------------------------------------------------- 1 | extern crate primal; 2 | 3 | use primal::Sieve; 4 | 5 | fn num_divisors(n: usize, primes: &Sieve) -> Option { 6 | primes.factor(n) 7 | .map(|factors| factors.into_iter().map(|(_, x)| x + 1).product()) 8 | .ok() 9 | } 10 | 11 | fn main() { 12 | println!("24 days of Rust - primal (day 2)"); 13 | let sieve = Sieve::new(10000); 14 | let suspect = 5273; 15 | println!("{} is prime: {}", suspect, sieve.is_prime(suspect)); 16 | let not_a_prime = 1024; 17 | println!("{} is prime: {}", not_a_prime, sieve.is_prime(not_a_prime)); 18 | let n = 1000; 19 | match sieve.primes_from(0).nth(n - 1) { 20 | Some(number) => println!("{}th prime is {}", n, number), 21 | None => println!("I don't know anything about {}th prime.", n), 22 | } 23 | println!("{:?}", sieve.factor(2610)); 24 | println!("{:?}", num_divisors(2610, &sieve)); 25 | } 26 | -------------------------------------------------------------------------------- /vol1/src/bin/day20.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_family = "unix")] 2 | extern crate zmq; 3 | 4 | #[cfg(target_family = "unix")] 5 | use zmq::{Context, Message, Error}; 6 | 7 | 8 | #[cfg(target_family = "unix")] 9 | fn run_client(ctx: &mut Context, addr: &str) -> Result<(), Error> { 10 | let sock = ctx.socket(zmq::REQ)?; 11 | sock.connect(addr)?; 12 | let payload = "Hello world!"; 13 | println!("-> {:?}", payload); 14 | let mut msg = Message::new()?; 15 | sock.send(payload.as_bytes(), 0)?; 16 | sock.recv(&mut msg, 0)?; 17 | let contents = msg.as_str().unwrap(); 18 | println!("<- {:?}", contents); 19 | Ok(()) 20 | } 21 | 22 | #[cfg(target_family = "unix")] 23 | fn run_server(ctx: &mut Context, addr: &str) -> Result<(), Error> { 24 | let sock = ctx.socket(zmq::REP)?; 25 | sock.bind(addr)?; 26 | let mut msg = Message::new()?; 27 | loop { 28 | if sock.recv(&mut msg, 0).is_ok() { 29 | sock.send(msg.as_str().unwrap().as_bytes(), 0)?; 30 | } 31 | } 32 | } 33 | 34 | #[cfg(target_family = "windows")] 35 | fn main() { 36 | println!("24 days of Rust - zmq (day 20)"); 37 | println!("This example does not work on Windows :("); 38 | } 39 | 40 | #[cfg(target_family = "unix")] 41 | fn main() { 42 | println!("24 days of Rust - zmq (day 20)"); 43 | let args = std::env::args().collect::>(); 44 | if args.len() < 2 { 45 | println!("Usage: {} (client|server)", args[0]); 46 | return; 47 | } 48 | let mut ctx = Context::new(); 49 | let addr = "tcp://127.0.0.1:25933"; 50 | if args[1] == "client" { 51 | println!("ZeroMQ client connecting to {}", addr); 52 | run_client(&mut ctx, addr).unwrap_or_else(|err| println!("{:?}", err)); 53 | } else { 54 | println!("ZeroMQ server listening on {}", addr); 55 | run_server(&mut ctx, addr).unwrap_or_else(|err| println!("{:?}", err)); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /vol1/src/bin/day21.rs: -------------------------------------------------------------------------------- 1 | extern crate crypto; 2 | extern crate rand; 3 | extern crate rustc_serialize; 4 | 5 | use crypto::aes::{self, KeySize}; 6 | use crypto::digest::Digest; 7 | use crypto::hmac::Hmac; 8 | use crypto::mac::Mac; 9 | use crypto::sha2::Sha256; 10 | use crypto::symmetriccipher::SynchronousStreamCipher; 11 | 12 | use rustc_serialize::base64::{STANDARD, ToBase64}; 13 | use rustc_serialize::hex::ToHex; 14 | 15 | use std::iter::repeat; 16 | use rand::{OsRng, Rng}; 17 | 18 | fn main() { 19 | println!("24 days of Rust - rust-crypto (day 21)"); 20 | let input = "Hello world!"; 21 | let mut sha = Sha256::new(); 22 | sha.input_str(input); 23 | println!("{}", sha.result_str()); 24 | let mut bytes: Vec = repeat(0u8).take(sha.output_bytes()).collect(); 25 | sha.result(&mut bytes[..]); 26 | println!("{}", bytes.to_base64(STANDARD)); 27 | 28 | let mut gen = OsRng::new().expect("Failed to get OS random generator"); 29 | let mut key: Vec = repeat(0u8).take(16).collect(); 30 | gen.fill_bytes(&mut key[..]); 31 | let mut nonce: Vec = repeat(0u8).take(16).collect(); 32 | gen.fill_bytes(&mut nonce[..]); 33 | println!("Key: {}", key.to_base64(STANDARD)); 34 | println!("Nonce: {}", nonce.to_base64(STANDARD)); 35 | let mut cipher = aes::ctr(KeySize::KeySize128, &key, &nonce); 36 | let secret = "I like Nickelback"; 37 | let mut output: Vec = repeat(0u8).take(secret.len()).collect(); 38 | cipher.process(secret.as_bytes(), &mut output[..]); 39 | println!("Ciphertext: {}", output.to_base64(STANDARD)); 40 | 41 | let mut hmac_key: Vec = repeat(0u8).take(32).collect(); 42 | gen.fill_bytes(&mut hmac_key); 43 | let message = "Ceterum censeo Carthaginem esse delendam"; 44 | println!("Message: {}", message); 45 | println!("HMAC key: {}", hmac_key.to_base64(STANDARD)); 46 | let mut hmac = Hmac::new(Sha256::new(), &hmac_key); 47 | hmac.input(message.as_bytes()); 48 | println!("HMAC digest: {}", hmac.result().code().to_hex()); 49 | } 50 | -------------------------------------------------------------------------------- /vol1/src/bin/day3.rs: -------------------------------------------------------------------------------- 1 | extern crate csv; 2 | extern crate rustc_serialize; 3 | 4 | use csv::{Reader, Writer}; 5 | 6 | #[derive(RustcDecodable, RustcEncodable)] 7 | struct Movie { 8 | title: String, 9 | bad_guy: String, 10 | pub_year: usize, 11 | } 12 | 13 | fn main() { 14 | println!("24 days of Rust - csv (day 3)"); 15 | let dollar_films = vec![ 16 | ("A Fistful of Dollars", "Rojo", 1964), 17 | ("For a Few Dollars More", "El Indio", 1965), 18 | ("The Good, the Bad and the Ugly", "Tuco", 1966), 19 | ]; 20 | let path = "westerns.csv"; 21 | let mut writer = Writer::from_file(path).unwrap(); 22 | for row in dollar_films { 23 | writer.encode(row).expect("CSV writer error"); 24 | } 25 | let movie = Movie { 26 | title: "Hang 'Em High".to_string(), 27 | bad_guy: "Wilson".to_string(), 28 | pub_year: 1968, 29 | }; 30 | writer.encode(movie).expect("CSV writer error"); 31 | writer.flush().expect("Flush error"); 32 | let mut reader = Reader::from_file(path).unwrap().has_headers(false); 33 | for row in reader.decode() { 34 | let row: (String, String, usize) = row.unwrap(); 35 | println!("{:?}", row); 36 | } 37 | let mut reader = Reader::from_file(path).unwrap().has_headers(false); 38 | for row in reader.decode() { 39 | let movie: Movie = row.unwrap(); 40 | println!( 41 | "{} was a bad guy in '{}' in {}", 42 | movie.bad_guy, 43 | movie.title, 44 | movie.pub_year 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /vol1/src/bin/day4.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![plugin(docopt_macros)] 3 | 4 | extern crate rustc_serialize; 5 | extern crate docopt; 6 | 7 | docopt!(Args, " 8 | Usage: wc [options] [] 9 | 10 | Options: 11 | -c, --bytes print the byte counts 12 | -m, --chars print the character counts 13 | -l, --lines print the newline counts 14 | -w, --words print the word counts 15 | -L, --max-line-length print the length of the longest line 16 | -h, --help display this help and exit 17 | -v, --version output version information and exit 18 | ", arg_file: Option); 19 | 20 | fn main() { 21 | println!("24 days of Rust - docopt (day 4)"); 22 | let docopt = Args::docopt(); 23 | println!("{:?}", docopt); 24 | let args: Args = docopt.decode().unwrap_or_else(|e| e.exit()); 25 | println!("Counting stuff in {}", args.arg_file.unwrap_or("standard input".to_string())); 26 | if args.flag_bytes { 27 | println!("Counting bytes!"); 28 | } 29 | if args.flag_chars { 30 | println!("Counting characters!"); 31 | } 32 | if args.flag_lines { 33 | println!("Counting lines!"); 34 | } 35 | if args.flag_words { 36 | println!("Counting words!"); 37 | } 38 | if args.flag_max_line_length { 39 | println!("Measuring the longest line!"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /vol1/src/bin/day5.rs: -------------------------------------------------------------------------------- 1 | extern crate hyper; 2 | extern crate rustc_serialize; 3 | extern crate url; 4 | 5 | use std::io::Read; 6 | use hyper::Client; 7 | use rustc_serialize::{Encodable, json}; 8 | use url::form_urlencoded; 9 | 10 | fn get_content(url: &str) -> hyper::Result { 11 | let client = Client::new(); 12 | let mut response = client.get(url).send()?; 13 | let mut buf = String::new(); 14 | response.read_to_string(&mut buf)?; 15 | Ok(buf) 16 | } 17 | 18 | type Query<'a> = Vec<(&'a str, &'a str)>; 19 | 20 | fn post_query(url: &str, query: Query) -> hyper::Result { 21 | let client = Client::new(); 22 | let body = form_urlencoded::Serializer::new(String::new()) 23 | .extend_pairs(query.iter()) 24 | .finish(); 25 | let mut response = client.post(url).body(&body[..]).send()?; 26 | let mut buf = String::new(); 27 | response.read_to_string(&mut buf)?; 28 | Ok(buf) 29 | } 30 | 31 | fn post_json(url: &str, payload: &T) -> hyper::Result 32 | where 33 | T: Encodable, 34 | { 35 | let client = Client::new(); 36 | let body = json::encode(payload).unwrap(); 37 | let mut response = client.post(url).body(&body[..]).send()?; 38 | let mut buf = String::new(); 39 | response.read_to_string(&mut buf)?; 40 | Ok(buf) 41 | } 42 | 43 | #[derive(RustcDecodable, RustcEncodable)] 44 | struct Movie { 45 | title: String, 46 | bad_guy: String, 47 | pub_year: usize, 48 | } 49 | 50 | fn main() { 51 | println!("24 days of Rust - hyper (day 5)"); 52 | println!("{:?}", get_content("http://httpbin.org/status/200")); 53 | let query = vec![("key", "value"), ("foo", "bar")]; 54 | println!("{}", post_query("http://httpbin.org/post", query).unwrap()); 55 | let movie = Movie { 56 | title: "You Only Live Twice".to_string(), 57 | bad_guy: "Blofeld".to_string(), 58 | pub_year: 1967, 59 | }; 60 | println!("{}", post_json("http://httpbin.org/post", &movie).unwrap()); 61 | } 62 | -------------------------------------------------------------------------------- /vol1/src/bin/day6.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![plugin(json_macros)] 3 | 4 | extern crate rustc_serialize; 5 | 6 | use rustc_serialize::Encodable; 7 | use rustc_serialize::json::{self, Encoder}; 8 | 9 | #[derive(RustcDecodable, RustcEncodable)] 10 | struct Photo { 11 | url: String, 12 | dimensions: (u32, u32), 13 | } 14 | 15 | #[derive(RustcDecodable, RustcEncodable)] 16 | struct User { 17 | name: String, 18 | post_count: u32, 19 | likes_burgers: bool, 20 | avatar: Option, 21 | } 22 | 23 | 24 | fn main() { 25 | println!("24 days of Rust - json (day 6)"); 26 | println!("{:?}", json::encode(&42)); 27 | println!( 28 | "{:?}", 29 | json::encode(&vec!["to", "be", "or", "not", "to", "be"]) 30 | ); 31 | println!("{:?}", json::encode(&Some(true))); 32 | let user = User { 33 | name: "Zbyszek".to_string(), 34 | post_count: 100u32, 35 | likes_burgers: true, 36 | avatar: Some(Photo { 37 | url: "http://lorempixel.com/160/160/".to_string(), 38 | dimensions: (160u32, 160u32), 39 | }), 40 | }; 41 | println!("{:?}", json::encode(&user)); 42 | let mut encoded = String::new(); 43 | { 44 | let mut encoder = Encoder::new_pretty(&mut encoded); 45 | user.encode(&mut encoder).expect("JSON encode error"); 46 | } 47 | println!("{}", encoded); 48 | let incoming_request = "{\"name\":\"John\",\"post_count\":2,\"likes_burgers\":false,\ 49 | \"avatar\":null}"; 50 | let decoded: User = json::decode(incoming_request).unwrap(); 51 | println!( 52 | "My name is {} and I {} burgers", 53 | decoded.name, 54 | if decoded.likes_burgers { 55 | "love" 56 | } else { 57 | "don't like" 58 | } 59 | ); 60 | assert!(decoded.avatar.is_none()); 61 | let new_request = "{\"id\":64,\"title\":\"24days\",\"stats\":{\"pageviews\":1500}}"; 62 | if let Ok(request_json) = json::Json::from_str(new_request) { 63 | if let Some(stats) = request_json.find("stats") { 64 | if let Some(pageviews) = stats.find("pageviews") { 65 | println!("Pageviews: {}", pageviews); 66 | } 67 | } 68 | } 69 | let config = json!({ 70 | "hostname": "localhost", 71 | "port": 6543, 72 | "allowed_methods": ["get", "post"], 73 | "limits": { 74 | "bandwidth": (500 * 16), 75 | "rate": null 76 | } 77 | }); 78 | println!("Configuration: {}", config); 79 | println!("Bandwidth: {}", config.search("bandwidth").unwrap()); 80 | } 81 | -------------------------------------------------------------------------------- /vol1/src/bin/day7.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate itertools; 3 | 4 | use itertools::Itertools; 5 | 6 | fn main() { 7 | println!("24 days of Rust - itertools (day 7)"); 8 | let words = "hello supercalifragilisticexpialidocious programmer".split(|c| c == ' '); 9 | words.foreach(|word| { 10 | println!("{} is {} characters long", word, word.len()) 11 | }); 12 | let even = (1..10).map(|x| x * 2); 13 | let odd = (1..5).map(|x| x * 2 + 1); 14 | println!("{:?}", even.interleave(odd).collect::>()); 15 | println!("{:?}", (1..10).intersperse(15).collect::>()); 16 | let numbers = 1..4; 17 | let chars = vec!['a', 'b', 'c']; 18 | for (i, c) in iproduct!(numbers, chars.iter()) { 19 | println!("{}: {}", i, c); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /vol1/src/bin/day9.rs: -------------------------------------------------------------------------------- 1 | extern crate anymap; 2 | 3 | use std::net::Ipv4Addr; 4 | use anymap::AnyMap; 5 | 6 | #[derive(Debug)] 7 | enum HostAddress { 8 | DomainName(String), 9 | Ip(Ipv4Addr), 10 | } 11 | 12 | #[derive(Debug)] 13 | struct Port(u32); 14 | 15 | #[derive(Debug)] 16 | struct ConnectionLimit(u32); 17 | 18 | fn main() { 19 | println!("24 days of Rust - anymap (day 9)"); 20 | let mut config = AnyMap::new(); 21 | config.insert(HostAddress::DomainName("siciarz.net".to_string())); 22 | config.insert(Port(666)); 23 | config.insert(ConnectionLimit(32)); 24 | println!("{:?}", config.get::()); 25 | println!("{:?}", config.get::()); 26 | assert!(config.get::().is_none()); 27 | assert!(config.get::().is_none()); 28 | config.insert(HostAddress::Ip(Ipv4Addr::new(127, 0, 0, 1))); 29 | println!("{:?}", config.get::()); 30 | if !config.contains::>() { 31 | println!("There's no optional 32-bit float in the configuration..."); 32 | } 33 | let dummy: Option = None; 34 | config.insert(dummy); 35 | if config.contains::>() { 36 | println!("There's an optional 32-bit float in the configuration..."); 37 | } 38 | if !config.contains::>() { 39 | println!("...but not an optional 64-bit float."); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /vol2/.env: -------------------------------------------------------------------------------- 1 | # email settings 2 | EMAIL_FROM=root@localhost 3 | EMAIL_BACKEND=smtp 4 | 5 | # for Diesel 6 | DATABASE_URL=postgres://diesel:diesel@localhost/diesel 7 | -------------------------------------------------------------------------------- /vol2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "24daysofrust_vol2" 3 | version = "0.0.1" 4 | authors = ["Zbigniew Siciarz "] 5 | 6 | [[bin]] 7 | name = "day2" 8 | 9 | [[bin]] 10 | name = "day3" 11 | 12 | [[bin]] 13 | name = "day4" 14 | 15 | [[bin]] 16 | name = "day5" 17 | 18 | [[bin]] 19 | name = "day6" 20 | 21 | [[bin]] 22 | name = "day7" 23 | 24 | [[bin]] 25 | name = "day8" 26 | 27 | [[bin]] 28 | name = "day9" 29 | 30 | [[bin]] 31 | name = "day10" 32 | 33 | [[bin]] 34 | name = "day11" 35 | 36 | [[bin]] 37 | name = "day12" 38 | 39 | [[bin]] 40 | name = "day13" 41 | 42 | [[bin]] 43 | name = "day14" 44 | 45 | [[bin]] 46 | name = "day15" 47 | 48 | [[bin]] 49 | name = "day16" 50 | 51 | [[bin]] 52 | name = "day17" 53 | 54 | [[bin]] 55 | name = "day18" 56 | 57 | [[bin]] 58 | name = "day19" 59 | 60 | [[bin]] 61 | name = "day20" 62 | 63 | [[bin]] 64 | name = "day21" 65 | 66 | #[[bin]] 67 | #name = "day22" 68 | 69 | [dependencies] 70 | app_dirs = "1.1.1" 71 | clap = "2.27.1" 72 | derive_builder = "0.5.0" 73 | dotenv = "0.10.1" 74 | #envy = "0.1.2" 75 | error-chain = "0.11.0" 76 | hound = "3.2.0" 77 | lazy_static = "0.2.11" 78 | leftpad = "0.2.0" 79 | #lettre = "0.6.1" 80 | markdown = "0.2.0" 81 | nom = "3.2.1" 82 | num = "0.1.40" 83 | num_cpus = "1.6.2" 84 | preferences = { git = "https://github.com/Eh2406/preferences-rs.git", rev = "4f426cf52da97d3b8914e10d4492f953d4304238" } 85 | phf = "0.7.21" 86 | phf_macros = "0.7.21" 87 | rand = "0.3.18" 88 | rayon = "0.9.0" 89 | reqwest = "0.8.1" 90 | rustfft = "2.0.0" 91 | serde = "1.0.21" 92 | serde_derive = "1.0.21" 93 | serde_json = "1.0.6" 94 | serde_yaml = "0.7.2" 95 | slog = "2.0.12" 96 | slog-json = "2.0.2" 97 | slog-term = "2.3.0" 98 | tempfile = "2.1.5" 99 | tera = "0.10.10" 100 | time = "0.1.38" 101 | uuid = { version = "0.5.1", features = ["v4"] } 102 | zip = "0.2.6" 103 | 104 | [target.'cfg(windows)'.dependencies] 105 | winreg = "0.4" 106 | 107 | [target.'cfg(unix)'.dependencies] 108 | cursive = { version = "0.7.4", default-features = false, features=["pancurses-backend"] } 109 | diesel = "0.16.0" 110 | diesel_codegen = { version = "0.16.0", features=["postgres"]} 111 | git2 = "0.6.8" 112 | rust-lzma = "0.2.1" 113 | -------------------------------------------------------------------------------- /vol2/data/playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | tasks: 4 | - name: tell the time 5 | command: date -------------------------------------------------------------------------------- /vol2/data/schedule.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "frequency": { 5 | "Hourly": 0 6 | }, 7 | "command": "mark_emails_as_read --all" 8 | }, 9 | { 10 | "frequency": { 11 | "Daily": 17 12 | }, 13 | "command": "./finish_work.sh" 14 | }, 15 | { 16 | "frequency": { 17 | "Monthly": 28 18 | }, 19 | "command": "python send_monthly_report.py > /dev/null" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /vol2/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsiciarz/24daysofrust/52eeea16bca1a89f599790318c569cef7b2ea712/vol2/migrations/.gitkeep -------------------------------------------------------------------------------- /vol2/migrations/20161206155312_initial_user/down.sql: -------------------------------------------------------------------------------- 1 | drop table users; 2 | -------------------------------------------------------------------------------- /vol2/migrations/20161206155312_initial_user/up.sql: -------------------------------------------------------------------------------- 1 | create table users ( 2 | id serial primary key, 3 | username varchar(255) not null, 4 | avatar varchar(255) null 5 | ); 6 | insert into users (username) values ('zbyszek'); 7 | -------------------------------------------------------------------------------- /vol2/migrations/20161206160342_create_photos/down.sql: -------------------------------------------------------------------------------- 1 | drop table photos; 2 | -------------------------------------------------------------------------------- /vol2/migrations/20161206160342_create_photos/up.sql: -------------------------------------------------------------------------------- 1 | create table photos ( 2 | id serial primary key, 3 | user_id integer not null references users (id), 4 | url varchar(255) not null 5 | ); 6 | 7 | insert into photos (user_id, url) values (1, 'http://lorempixel.com/output/cats-q-c-640-480-10.jpg'); 8 | -------------------------------------------------------------------------------- /vol2/migrations/20161207095753_add_tags/down.sql: -------------------------------------------------------------------------------- 1 | alter table photos drop column tags; 2 | -------------------------------------------------------------------------------- /vol2/migrations/20161207095753_add_tags/up.sql: -------------------------------------------------------------------------------- 1 | alter table photos add column tags text[] not null default '{}'; 2 | -------------------------------------------------------------------------------- /vol2/src/bin/day10.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate nom; 3 | 4 | use std::str; 5 | 6 | #[derive(Debug)] 7 | enum Method { 8 | GET, 9 | POST, 10 | } 11 | 12 | #[derive(Debug)] 13 | struct Request { 14 | method: Method, 15 | url: String, 16 | version: String, 17 | } 18 | 19 | named!(parse_method_v1, alt!(tag!("GET") | tag!("POST"))); 20 | 21 | named!(parse_method_v2<&[u8], &str>, 22 | alt!(map_res!(tag!("GET"), str::from_utf8) | map_res!(tag!("POST"), str::from_utf8))); 23 | 24 | named!(parse_method<&[u8], Method>, 25 | alt!(map!(tag!("GET"), |_| Method::GET) | map!(tag!("POST"), |_| Method::POST))); 26 | 27 | named!(parse_request<&[u8], Request>, ws!(do_parse!( 28 | method: parse_method >> 29 | url: map_res!(take_until!(" "), str::from_utf8) >> 30 | tag!("HTTP/") >> 31 | version: map_res!(take_until!("\r"), str::from_utf8) >> 32 | (Request { method: method, url: url.into(), version: version.into() }) 33 | ))); 34 | 35 | fn main() { 36 | println!("24 Days of Rust vol. 2 - nom, part 1"); 37 | let first_line = b"GET /home/ HTTP/1.1\r\n"; 38 | println!("{:?}", parse_method_v1(&first_line[..])); 39 | println!("{:?}", parse_method_v2(&first_line[..])); 40 | println!("{:?}", parse_method(&first_line[..])); 41 | println!("{:?}", parse_request(&first_line[..])); 42 | let bad_line = b"GT /home/ HTTP/1.1\r\n"; 43 | println!("{:?}", parse_request(&bad_line[..])); 44 | } 45 | -------------------------------------------------------------------------------- /vol2/src/bin/day11.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate nom; 3 | 4 | use nom::{be_u16, be_u64}; 5 | 6 | #[derive(Debug)] 7 | enum OpCode { 8 | Continue, 9 | Text, 10 | Binary, 11 | Close, 12 | Ping, 13 | Pong, 14 | Reserved, 15 | } 16 | 17 | impl From for OpCode { 18 | fn from(opcode: u8) -> OpCode { 19 | match opcode { 20 | 0 => OpCode::Continue, 21 | 1 => OpCode::Text, 22 | 2 => OpCode::Binary, 23 | 8 => OpCode::Close, 24 | 9 => OpCode::Ping, 25 | 10 => OpCode::Pong, 26 | _ => OpCode::Reserved, 27 | } 28 | } 29 | } 30 | 31 | #[derive(Debug)] 32 | struct WebSocketFrame { 33 | fin: bool, 34 | opcode: OpCode, 35 | mask: bool, 36 | length: u64, 37 | masking_key: u16, 38 | payload: Vec, 39 | } 40 | 41 | named!(parse_frame<&[u8], WebSocketFrame>, do_parse!( 42 | first_byte: bits!(tuple!(take_bits!(u8, 1), take_bits!(u8, 3), take_bits!(u8, 4))) >> 43 | mask_and_length: bits!(tuple!(take_bits!(u8, 1), take_bits!(u8, 7))) >> 44 | extended_length: be_u64 >> 45 | length: value!(match mask_and_length.1 { 46 | 127u8 => extended_length, 47 | 126u8 => extended_length & 0xFFFF_0000_0000_0000u64 >> 24, 48 | _ => mask_and_length.1 as u64 49 | }) >> 50 | masking_key: be_u16 >> 51 | payload: take!(length) >> 52 | (WebSocketFrame { 53 | fin: first_byte.0 == 1, 54 | opcode: OpCode::from(first_byte.2), 55 | mask: mask_and_length.0 == 1, 56 | length: length, 57 | masking_key: masking_key, 58 | payload: payload.into(), 59 | }) 60 | )); 61 | 62 | fn main() { 63 | println!("24 Days of Rust vol. 2 - nom, part 2"); 64 | let frame = vec![ 65 | 0b10000001, 66 | 0b10000011, 67 | 0b00000000, 68 | 0b00000000, 69 | 0b00000000, 70 | 0b00000000, 71 | 0b00000000, 72 | 0b00000000, 73 | 0b00000000, 74 | 0b00000000, 75 | 0b00010010, 76 | 0b10111001, 77 | 0b00000001, 78 | 0b00000010, 79 | 0b00000011, 80 | ]; // [1, 2, 3] 81 | println!("{:?}", parse_frame(&frame[..])); 82 | } 83 | -------------------------------------------------------------------------------- /vol2/src/bin/day12.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate clap; 3 | 4 | #[macro_use] 5 | extern crate slog; 6 | extern crate slog_term; 7 | 8 | use slog::Drain; 9 | use std::process; 10 | use clap::{Arg, ArgMatches, App, SubCommand}; 11 | 12 | arg_enum! { 13 | #[derive(Debug)] 14 | enum Algorithm { 15 | SHA1, 16 | SHA256, 17 | Argon2 18 | } 19 | } 20 | 21 | fn run(matches: ArgMatches) -> Result<(), String> { 22 | let min_log_level = match matches.occurrences_of("verbose") { 23 | 0 => slog::Level::Info, 24 | 1 => slog::Level::Debug, 25 | 2 | _ => slog::Level::Trace, 26 | }; 27 | let decorator = slog_term::PlainSyncDecorator::new(std::io::stderr()); 28 | let drain = slog_term::FullFormat::new(decorator) 29 | .build() 30 | .filter_level(min_log_level) 31 | .fuse(); 32 | let logger = slog::Logger::root(drain, o!()); 33 | trace!(logger, "app_setup"); 34 | // setting up app... 35 | debug!(logger, "load_configuration"); 36 | trace!(logger, "app_setup_complete"); 37 | // starting processing... 38 | info!(logger, "processing_started"); 39 | match matches.subcommand() { 40 | ("analyse", Some(m)) => run_analyse(m, &logger), 41 | ("verify", Some(m)) => run_verify(m, &logger), 42 | _ => Ok(()), 43 | } 44 | } 45 | 46 | fn run_analyse(matches: &ArgMatches, parent_logger: &slog::Logger) -> Result<(), String> { 47 | let logger = parent_logger.new(o!("command" => "analyse")); 48 | let input = matches.value_of("input-file").unwrap(); 49 | debug!(logger, "analysis_started"; "input_file" => input); 50 | Ok(()) 51 | } 52 | 53 | fn run_verify(matches: &ArgMatches, parent_logger: &slog::Logger) -> Result<(), String> { 54 | let logger = parent_logger.new(o!("command" => "verify")); 55 | let algorithm = value_t!(matches.value_of("algorithm"), Algorithm).unwrap(); 56 | debug!(logger, "verification_started"; "algorithm" => format!("{:?}", algorithm)); 57 | Ok(()) 58 | } 59 | 60 | fn main() { 61 | println!("24 Days of Rust vol. 2 - clap"); 62 | let matches = App::new("24daysofrust") 63 | .version("0.1") 64 | .author("Zbigniew Siciarz") 65 | .about("learn you some Rust!") 66 | .arg(Arg::with_name("verbose").short("v").multiple(true).help( 67 | "verbosity level", 68 | )) 69 | .subcommand( 70 | SubCommand::with_name("analyse") 71 | .about("Analyses the data from file") 72 | .arg( 73 | Arg::with_name("input-file") 74 | .short("i") 75 | .default_value("default.csv") 76 | .value_name("FILE"), 77 | ), 78 | ) 79 | .subcommand( 80 | SubCommand::with_name("verify") 81 | .about("Verifies the data") 82 | .arg( 83 | Arg::with_name("algorithm") 84 | .short("a") 85 | .help("Hash algorithm to use") 86 | .possible_values(&Algorithm::variants()) 87 | .required(true) 88 | .value_name("ALGORITHM"), 89 | ), 90 | ) 91 | .get_matches(); 92 | if let Err(e) = run(matches) { 93 | println!("Application error: {}", e); 94 | process::exit(1); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /vol2/src/bin/day13.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_family = "unix")] 2 | extern crate lzma; 3 | extern crate zip; 4 | 5 | use std::fs::{File, metadata}; 6 | use std::io::{Read, Seek, Write}; 7 | use zip::read::{ZipArchive, ZipFile}; 8 | use zip::result::ZipResult; 9 | use zip::write::{FileOptions, ZipWriter}; 10 | 11 | #[cfg(target_family = "unix")] 12 | use lzma::{LzmaWriter, LzmaError}; 13 | 14 | static FILE_CONTENTS: &'static [u8] = include_bytes!("../../Cargo.lock"); 15 | 16 | fn create_zip_archive(buf: &mut T) -> ZipResult<()> { 17 | let mut writer = ZipWriter::new(buf); 18 | writer.start_file("example.txt", FileOptions::default())?; 19 | writer.write_all(FILE_CONTENTS)?; 20 | writer.finish()?; 21 | Ok(()) 22 | } 23 | 24 | fn browse_zip_archive(buf: &mut T, browse_func: F) -> ZipResult> 25 | where 26 | T: Read + Seek, 27 | F: Fn(&ZipFile) -> ZipResult, 28 | { 29 | let mut archive = ZipArchive::new(buf)?; 30 | (0..archive.len()) 31 | .map(|i| archive.by_index(i).and_then(|file| browse_func(&file))) 32 | .collect() 33 | } 34 | 35 | fn create_bz2_archive(buf: &mut T) -> ZipResult<()> { 36 | let mut writer = ZipWriter::new(buf); 37 | writer.start_file( 38 | "example.txt", 39 | FileOptions::default().compression_method( 40 | zip::CompressionMethod::Bzip2, 41 | ), 42 | )?; 43 | writer.write_all(FILE_CONTENTS)?; 44 | writer.finish()?; 45 | Ok(()) 46 | } 47 | 48 | #[cfg(target_family = "unix")] 49 | fn create_xz_archive(buf: &mut T) -> Result<(), LzmaError> { 50 | let mut writer = LzmaWriter::new_compressor(buf, 6)?; 51 | writer.write_all(FILE_CONTENTS)?; 52 | writer.finish()?; 53 | Ok(()) 54 | } 55 | 56 | #[cfg(target_family = "windows")] 57 | fn create_xz_archive(_: &mut T) -> Result<(), ()> { 58 | Ok(()) 59 | } 60 | 61 | fn main() { 62 | println!("24 Days of Rust vol. 2 - zip"); 63 | let mut file = File::create("example.zip").expect("Couldn't create file"); 64 | create_zip_archive(&mut file).expect("Couldn't create archive"); 65 | 66 | let mut file = File::open("example.zip").expect("Couldn't open file"); 67 | let files = browse_zip_archive(&mut file, |f| { 68 | Ok(format!( 69 | "{}: {} -> {}", 70 | f.name(), 71 | f.size(), 72 | f.compressed_size() 73 | )) 74 | }); 75 | println!("{:?}", files); 76 | 77 | let mut file = File::create("example.bz2").expect("Couldn't create file"); 78 | create_bz2_archive(&mut file).expect("Couldn't create archive"); 79 | 80 | let mut file = File::create("example.xz").expect("Couldn't create file"); 81 | create_xz_archive(&mut file).expect("Couldn't create archive"); 82 | 83 | if let Ok(meta) = metadata("example.zip") { 84 | println!("ZIP file size: {} bytes", meta.len()); 85 | } 86 | if let Ok(meta) = metadata("example.bz2") { 87 | println!("BZ2 file size: {} bytes", meta.len()); 88 | } 89 | if let Ok(meta) = metadata("example.xz") { 90 | println!("XZ file size: {} bytes", meta.len()); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /vol2/src/bin/day14.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_family = "unix")] 2 | extern crate cursive; 3 | 4 | #[cfg(target_family = "unix")] 5 | use cursive::Cursive; 6 | #[cfg(target_family = "unix")] 7 | use cursive::traits::*; 8 | #[cfg(target_family = "unix")] 9 | use cursive::views::{Dialog, DummyView, LinearLayout, SelectView, TextView}; 10 | 11 | #[cfg(target_family = "unix")] 12 | use std::fs::{self, DirEntry, File}; 13 | #[cfg(target_family = "unix")] 14 | use std::io::Read; 15 | #[cfg(target_family = "unix")] 16 | use std::path::Path; 17 | 18 | #[cfg(target_family = "windows")] 19 | fn main() { 20 | println!("TODO"); 21 | } 22 | 23 | #[cfg(target_family = "unix")] 24 | fn file_picker(directory: D) -> SelectView 25 | where 26 | D: AsRef, 27 | { 28 | let mut view = SelectView::new(); 29 | for entry in fs::read_dir(directory).expect("can't read directory") { 30 | if let Ok(e) = entry { 31 | let file_name = e.file_name().into_string().unwrap(); 32 | view.add_item(file_name, e); 33 | } 34 | } 35 | view.on_select(update_status).on_submit(load_contents) 36 | } 37 | 38 | #[cfg(target_family = "unix")] 39 | fn update_status(app: &mut Cursive, entry: &DirEntry) { 40 | let mut status_bar = app.find_id::("status").unwrap(); 41 | let file_name = entry.file_name().into_string().unwrap(); 42 | let file_size = entry.metadata().unwrap().len(); 43 | let content = format!("{}: {} bytes", file_name, file_size); 44 | status_bar.set_content(content); 45 | } 46 | 47 | #[cfg(target_family = "unix")] 48 | fn load_contents(app: &mut Cursive, entry: &DirEntry) { 49 | let mut text_view = app.find_id::("contents").unwrap(); 50 | let content = if entry.metadata().unwrap().is_dir() { 51 | "

".to_string() 52 | } else { 53 | let mut buf = String::new(); 54 | let _ = File::open(entry.file_name()) 55 | .and_then(|mut f| f.read_to_string(&mut buf)) 56 | .map_err(|e| buf = format!("Error: {}", e)); 57 | buf 58 | }; 59 | text_view.set_content(content); 60 | } 61 | 62 | #[cfg(target_family = "unix")] 63 | fn main() { 64 | println!("24 Days of Rust vol. 2 - cursive"); 65 | let mut app = Cursive::new(); 66 | let mut panes = LinearLayout::horizontal(); 67 | let picker = file_picker("."); 68 | panes.add_child(picker.fixed_size((30, 25))); 69 | panes.add_child(DummyView); 70 | panes.add_child( 71 | TextView::new("file contents") 72 | .with_id("contents") 73 | .fixed_size((50, 25)), 74 | ); 75 | let mut layout = LinearLayout::vertical(); 76 | layout.add_child(panes); 77 | layout.add_child( 78 | TextView::new("status") 79 | .scrollable(false) 80 | .with_id("status") 81 | .fixed_size((80, 1)), 82 | ); 83 | app.add_layer(Dialog::around(layout).button("Quit", |a| a.quit())); 84 | app.run(); 85 | } 86 | -------------------------------------------------------------------------------- /vol2/src/bin/day15.rs: -------------------------------------------------------------------------------- 1 | extern crate markdown; 2 | extern crate serde_json; 3 | #[macro_use] 4 | extern crate tera; 5 | 6 | use std::collections::HashMap; 7 | use tera::{Context, Result, Value, to_value}; 8 | 9 | pub fn markdown_filter(value: Value, _: HashMap) -> Result { 10 | let s = try_get_value!("upper", "value", String, value); 11 | Ok(to_value(markdown::to_html(s.as_str()))?) 12 | } 13 | 14 | const LIPSUM: &'static str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut \ 15 | labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco \ 16 | laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in \ 17 | voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat \ 18 | cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"; 19 | 20 | fn main() { 21 | println!("24 days of Rust vol. 2 - tera"); 22 | let mut tera = compile_templates!("templates/**/*"); 23 | let mut ctx = Context::new(); 24 | ctx.add("title", &"hello world!"); 25 | ctx.add("content", &LIPSUM); 26 | ctx.add( 27 | "todos", 28 | &vec!["buy milk", "walk the dog", "write about tera"], 29 | ); 30 | let rendered = tera.render("index.html", &ctx).expect( 31 | "Failed to render template", 32 | ); 33 | println!("{}", rendered); 34 | 35 | tera.register_filter("markdown", markdown_filter); 36 | let mut ctx = Context::new(); 37 | ctx.add("content", &"**bold** and `beautiful`"); 38 | let rendered = tera.render("blog.html", &ctx).expect( 39 | "Failed to render template", 40 | ); 41 | println!("{}", rendered); 42 | 43 | let mut config = HashMap::new(); 44 | config.insert("hostname", "127.0.0.1"); 45 | config.insert("user", "root"); 46 | config.insert("email", "NAME@example.com"); 47 | let mut ctx = Context::new(); 48 | ctx.add("config", &config); 49 | let rendered = tera.render("config.ini", &ctx).expect( 50 | "Failed to render template", 51 | ); 52 | println!("{}", rendered); 53 | } 54 | -------------------------------------------------------------------------------- /vol2/src/bin/day16.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_family = "unix")] 2 | extern crate git2; 3 | extern crate time; 4 | 5 | #[cfg(target_family = "unix")] 6 | use git2::{Commit, Direction, ObjectType, Oid, Repository, Signature}; 7 | #[cfg(target_family = "unix")] 8 | use std::fs::{File, canonicalize}; 9 | #[cfg(target_family = "unix")] 10 | use std::io::Write; 11 | #[cfg(target_family = "unix")] 12 | use std::path::Path; 13 | 14 | #[cfg(target_family = "unix")] 15 | fn find_last_commit(repo: &Repository) -> Result { 16 | let obj = repo.head()?.resolve()?.peel(ObjectType::Commit)?; 17 | obj.into_commit().map_err(|_| { 18 | git2::Error::from_str("Couldn't find commit") 19 | }) 20 | } 21 | 22 | #[cfg(target_family = "unix")] 23 | fn display_commit(commit: &Commit) { 24 | let timestamp = commit.time().seconds(); 25 | let tm = time::at(time::Timespec::new(timestamp, 0)); 26 | println!( 27 | "commit {}\nAuthor: {}\nDate: {}\n\n {}", 28 | commit.id(), 29 | commit.author(), 30 | tm.rfc822(), 31 | commit.message().unwrap_or("no commit message") 32 | ); 33 | } 34 | 35 | #[cfg(target_family = "unix")] 36 | fn add_and_commit(repo: &Repository, path: &Path, message: &str) -> Result { 37 | let mut index = repo.index()?; 38 | index.add_path(path)?; 39 | let oid = index.write_tree()?; 40 | let signature = Signature::now("Zbigniew Siciarz", "zbigniew@siciarz.net")?; 41 | let parent_commit = find_last_commit(repo)?; 42 | let tree = repo.find_tree(oid)?; 43 | repo.commit( 44 | Some("HEAD"), // point HEAD to our new commit 45 | &signature, // author 46 | &signature, // committer 47 | message, // commit message 48 | &tree, // tree 49 | &[&parent_commit], 50 | ) // parents 51 | } 52 | 53 | #[cfg(target_family = "unix")] 54 | fn push(repo: &Repository, url: &str) -> Result<(), git2::Error> { 55 | let mut remote = match repo.find_remote("origin") { 56 | Ok(r) => r, 57 | Err(_) => repo.remote("origin", url)?, 58 | }; 59 | remote.connect(Direction::Push)?; 60 | remote.push(&["refs/heads/master:refs/heads/master"], None) 61 | } 62 | 63 | #[cfg(target_family = "windows")] 64 | fn main() { 65 | println!("TODO"); 66 | } 67 | 68 | #[cfg(target_family = "unix")] 69 | fn main() { 70 | println!("24 Days of Rust vol. 2 - git2"); 71 | let repo_root = std::env::args().nth(1).unwrap_or(".".to_string()); 72 | let repo = Repository::open(repo_root.as_str()).expect("Couldn't open repository"); 73 | println!("{} state={:?}", repo.path().display(), repo.state()); 74 | let commit = find_last_commit(&repo).expect("Couldn't find last commit"); 75 | display_commit(&commit); 76 | 77 | let relative_path = Path::new("example.txt"); 78 | { 79 | let file_path = Path::new(repo_root.as_str()).join(relative_path); 80 | let mut file = File::create(file_path.clone()).expect("Couldn't create file"); 81 | file.write_all(b"Hello git2").unwrap(); 82 | } 83 | let commit_id = add_and_commit(&repo, relative_path, "Add example text file") 84 | .expect("Couldn't add file to repo"); 85 | println!("New commit: {}", commit_id); 86 | 87 | let remote_url = format!( 88 | "file://{}", 89 | canonicalize("../../git_remote").unwrap().display() 90 | ); 91 | println!("Pushing to: {}", remote_url); 92 | push(&repo, remote_url.as_str()).expect("Couldn't push to remote repo"); 93 | } 94 | -------------------------------------------------------------------------------- /vol2/src/bin/day17.rs: -------------------------------------------------------------------------------- 1 | #![feature(custom_attribute)] 2 | 3 | #[cfg(target_family = "unix")] 4 | #[macro_use] 5 | extern crate diesel; 6 | 7 | #[cfg(target_family = "unix")] 8 | #[macro_use] 9 | extern crate diesel_codegen; 10 | 11 | #[cfg(target_family = "unix")] 12 | extern crate dotenv; 13 | 14 | #[cfg(target_family = "unix")] 15 | use diesel::prelude::*; 16 | #[cfg(target_family = "unix")] 17 | use diesel::pg::PgConnection; 18 | #[cfg(target_family = "unix")] 19 | use diesel::expression::sql_literal::sql; 20 | 21 | #[cfg(target_family = "unix")] 22 | use std::env; 23 | 24 | #[cfg(target_family = "unix")] 25 | mod schema { 26 | infer_schema!("dotenv:DATABASE_URL"); 27 | } 28 | 29 | #[cfg(target_family = "unix")] 30 | use schema::*; 31 | 32 | #[cfg(target_family = "unix")] 33 | #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] 34 | pub struct User { 35 | pub id: i32, 36 | pub username: String, 37 | pub avatar: Option, 38 | } 39 | 40 | #[cfg(target_family = "unix")] 41 | #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] 42 | #[belongs_to(User)] 43 | pub struct Photo { 44 | pub id: i32, 45 | pub user_id: i32, 46 | pub url: String, 47 | pub tags: Vec, 48 | } 49 | 50 | #[cfg(target_family = "unix")] 51 | #[derive(Debug, Insertable)] 52 | #[table_name = "photos"] 53 | pub struct NewPhoto { 54 | pub user_id: i32, 55 | pub url: String, 56 | } 57 | 58 | #[cfg(target_family = "unix")] 59 | impl User { 60 | fn new_photo(&self, url: &str) -> NewPhoto { 61 | NewPhoto { 62 | user_id: self.id, 63 | url: url.to_string(), 64 | } 65 | } 66 | } 67 | 68 | 69 | #[cfg(target_family = "windows")] 70 | fn main() { 71 | println!("TODO"); 72 | } 73 | 74 | #[cfg(target_family = "unix")] 75 | fn main() { 76 | println!("24 Days of Rust vol. 2 - diesel"); 77 | dotenv::dotenv().ok(); 78 | let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); 79 | let conn = PgConnection::establish(&database_url).expect("Couldn't connect to DB"); 80 | 81 | let me = users::table.find(1).first::(&conn).expect( 82 | "Error loading user", 83 | ); 84 | println!("{:?}", me); 85 | 86 | let deleted_count = diesel::delete(photos::table.filter(photos::id.gt(1))) 87 | .execute(&conn) 88 | .expect("Failed to clean up photos"); 89 | println!("Deleted {} photo(s)", deleted_count); 90 | 91 | let photo_count: i64 = Photo::belonging_to(&me).count().first(&conn).expect( 92 | "Error counting photos", 93 | ); 94 | println!("User {} has {} photo(s)", me.username, photo_count); 95 | print_sql!(Photo::belonging_to(&me).count()); 96 | let my_photos = Photo::belonging_to(&me).load::(&conn).expect( 97 | "Error loading photos", 98 | ); 99 | println!("{:?}", my_photos); 100 | 101 | let photos: Vec<(Photo, User)> = photos::table.inner_join(users::table).load(&conn).expect( 102 | "Error loading photos", 103 | ); 104 | for (photo, user) in photos { 105 | println!("Photo #{} by {}", photo.id, user.username); 106 | } 107 | 108 | let users_with_cat_photos: Vec = users::table 109 | .select(users::username) 110 | .inner_join(photos::table) 111 | .filter(photos::url.like("%cat%")) 112 | .group_by(users::id) 113 | .load(&conn) 114 | .expect("Error loading users"); 115 | println!("{:?}", users_with_cat_photos); 116 | 117 | let photo = me.new_photo("http://lorempixel.com/output/cats-q-c-640-480-8.jpg"); 118 | let mut inserted_photo = diesel::insert(&photo) 119 | .into(photos::table) 120 | .get_result::(&conn) 121 | .expect("Failed to insert photo"); 122 | println!("{:?}", inserted_photo); 123 | 124 | inserted_photo.tags = vec!["cute".to_string(), "kitten".to_string()]; 125 | let updated_photo: Photo = inserted_photo.save_changes(&conn).expect( 126 | "Error updating photo", 127 | ); 128 | println!("{:?}", updated_photo); 129 | 130 | let cute_cat_count: i64 = photos::table 131 | .filter(photos::tags.contains(vec!["cute", "kitten"])) 132 | .count() 133 | .get_result(&conn) 134 | .expect("Error counting cute kittens"); 135 | println!("There's {} photos of cute cats", cute_cat_count); 136 | 137 | let cute_cat_count: i64 = sql( 138 | "select count(*) from photos \ 139 | where tags @> array['cute', 'kitten']", 140 | ).get_result(&conn) 141 | .expect("Error executing raw SQL"); 142 | println!("There's {} photos of cute cats", cute_cat_count); 143 | } 144 | -------------------------------------------------------------------------------- /vol2/src/bin/day18.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "1024"] 2 | 3 | #[macro_use] 4 | extern crate error_chain; 5 | #[macro_use] 6 | extern crate serde_derive; 7 | extern crate serde_json; 8 | extern crate tempfile; 9 | 10 | use std::fmt; 11 | use std::fs::File; 12 | use std::io::Write; 13 | use std::path::Path; 14 | use std::process::Command; 15 | 16 | mod errors { 17 | error_chain!{} 18 | } 19 | 20 | use errors::*; 21 | 22 | #[derive(Debug, Serialize, Deserialize)] 23 | enum Frequency { 24 | Hourly(i32), 25 | Daily(i32), 26 | Monthly(i32), 27 | } 28 | 29 | #[derive(Debug, Serialize, Deserialize)] 30 | struct Rule { 31 | frequency: Frequency, 32 | command: String, 33 | } 34 | 35 | #[derive(Debug, Serialize, Deserialize)] 36 | struct Schedule { 37 | rules: Vec, 38 | } 39 | 40 | impl fmt::Display for Frequency { 41 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 42 | match *self { 43 | Frequency::Hourly(minutes) => write!(f, "{} * * * *", minutes), 44 | Frequency::Daily(hour) => write!(f, "* {} * * *", hour), 45 | Frequency::Monthly(day) => write!(f, "* * {} * *", day), 46 | } 47 | } 48 | } 49 | 50 | impl fmt::Display for Rule { 51 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 52 | write!(f, "{} {}\n", self.frequency, self.command) 53 | } 54 | } 55 | 56 | impl fmt::Display for Schedule { 57 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 58 | let _ = write!(f, "# Automatically generated by json2cron\n"); 59 | for rule in &self.rules { 60 | let _ = write!(f, "{}", rule); 61 | } 62 | Ok(()) 63 | } 64 | } 65 | 66 | fn main() { 67 | println!("24 Days of Rust, volume 2 - error_chain"); 68 | if let Err(ref e) = run() { 69 | println!("error: {}", e); 70 | for e in e.iter().skip(1) { 71 | println!("caused by: {}", e); 72 | } 73 | if let Some(backtrace) = e.backtrace() { 74 | println!("backtrace: {:?}", backtrace); 75 | } 76 | std::process::exit(1); 77 | } 78 | } 79 | 80 | fn run() -> Result<()> { 81 | let schedule = load_schedule("data/schedule.json").chain_err( 82 | || "failed to load schedule", 83 | )?; 84 | if schedule.rules.is_empty() { 85 | bail!("the schedule is empty"); 86 | } 87 | println!("{:#?}", schedule); 88 | println!("{}", schedule); 89 | update_crontab(&schedule).chain_err(|| "failed to update crontab") 90 | } 91 | 92 | fn load_schedule>(path: P) -> Result { 93 | let file = File::open(path).chain_err(|| "failed to open input file")?; 94 | serde_json::from_reader(&file).chain_err(|| "failed to read JSON") 95 | } 96 | 97 | fn update_crontab(schedule: &Schedule) -> Result<()> { 98 | let mut file = tempfile::NamedTempFile::new().chain_err( 99 | || "failed to create a temporary file", 100 | )?; 101 | let schedule_str = format!("{}", schedule); 102 | file.write_all(&schedule_str.into_bytes()[..]).chain_err( 103 | || "failed to write schedule", 104 | )?; 105 | let path = file.path().to_str().ok_or("temporary path is not UTF-8")?; 106 | Command::new("crontab").arg(path).spawn().chain_err( 107 | || "failed to run crontab command", 108 | )?; 109 | Ok(()) 110 | } 111 | -------------------------------------------------------------------------------- /vol2/src/bin/day19.rs: -------------------------------------------------------------------------------- 1 | extern crate leftpad; 2 | 3 | use leftpad::{left_pad, left_pad_char}; 4 | 5 | fn main() { 6 | println!("24 Days of Rust vol. 2 - leftpad"); 7 | println!("{}", left_pad("pad me", 20)); 8 | println!("{}", left_pad("pad me again", 20)); 9 | println!("{}", left_pad_char("tick", 20, '✓')); 10 | } 11 | -------------------------------------------------------------------------------- /vol2/src/bin/day2.rs: -------------------------------------------------------------------------------- 1 | extern crate hound; 2 | extern crate num; 3 | extern crate rustfft; 4 | 5 | use std::f32::consts::PI; 6 | use hound::{SampleFormat, WavReader, WavSamples, WavSpec, WavWriter}; 7 | use num::complex::Complex; 8 | use rustfft::FFTplanner; 9 | 10 | 11 | trait Signal { 12 | fn energy(self) -> f64; 13 | } 14 | 15 | impl<'a, R> Signal for WavSamples<'a, R, i16> 16 | where 17 | R: std::io::Read, 18 | { 19 | fn energy(self) -> f64 { 20 | self.map(|x| { 21 | let sample = x.unwrap() as f64; 22 | sample * sample 23 | }).sum() 24 | } 25 | } 26 | 27 | 28 | fn generate_sine(filename: &str, frequency: f32, duration: u32) { 29 | let header = WavSpec { 30 | channels: 1, 31 | sample_rate: 44100, 32 | bits_per_sample: 16, 33 | sample_format: SampleFormat::Int, 34 | }; 35 | let mut writer = WavWriter::create(filename, header).expect("Failed to created WAV writer"); 36 | let num_samples = duration * header.sample_rate; 37 | let signal_amplitude = 16384f32; 38 | for n in 0..num_samples { 39 | let t: f32 = n as f32 / header.sample_rate as f32; 40 | let x = signal_amplitude * (t * frequency * 2.0 * PI).sin(); 41 | writer.write_sample(x as i16).unwrap(); 42 | } 43 | } 44 | 45 | fn find_spectral_peak(filename: &str) -> Option { 46 | let mut reader = WavReader::open(filename).expect("Failed to open WAV file"); 47 | let num_samples = reader.len() as usize; 48 | let mut planner = FFTplanner::new(false); 49 | let fft = planner.plan_fft(num_samples); 50 | let mut signal = reader 51 | .samples::() 52 | .map(|x| Complex::new(x.unwrap() as f32, 0f32)) 53 | .collect::>(); 54 | let mut spectrum = signal.clone(); 55 | fft.process(&mut signal[..], &mut spectrum[..]); 56 | let max_peak = spectrum 57 | .iter() 58 | .take(num_samples / 2) 59 | .enumerate() 60 | .max_by_key(|&(_, freq)| freq.norm() as u32); 61 | if let Some((i, _)) = max_peak { 62 | let bin = 44100f32 / num_samples as f32; 63 | Some(i as f32 * bin) 64 | } else { 65 | None 66 | } 67 | } 68 | 69 | fn main() { 70 | println!("24 Days of Rust vol. 2 - hound"); 71 | generate_sine("test.wav", 1000f32, 5); 72 | let mut reader = WavReader::open("test.wav").expect("Failed to open WAV file"); 73 | let samples = reader.samples::(); 74 | println!("Signal energy: {}", samples.energy()); 75 | 76 | if let Some(peak) = find_spectral_peak("test.wav") { 77 | println!("Max frequency: {} Hz", peak); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /vol2/src/bin/day20.rs: -------------------------------------------------------------------------------- 1 | extern crate reqwest; 2 | #[macro_use] 3 | extern crate serde_derive; 4 | extern crate serde_json; 5 | 6 | use std::collections::HashMap; 7 | use std::io::{copy, Read, stdout}; 8 | 9 | use reqwest::header::{Authorization, Basic}; 10 | 11 | #[derive(Debug, Deserialize)] 12 | struct Move { 13 | name: String, 14 | } 15 | 16 | #[derive(Debug, Deserialize)] 17 | struct PokemonMove { 18 | #[serde(rename = "move")] 19 | move_: Move, 20 | } 21 | 22 | #[derive(Debug, Deserialize)] 23 | struct Pokemon { 24 | id: i32, 25 | name: String, 26 | height: i32, 27 | weight: i32, 28 | moves: Vec, 29 | } 30 | 31 | fn main() { 32 | println!("24 Days of Rust vol. 2 - reqwest"); 33 | let mut response = 34 | reqwest::get("https://httpbin.org/status/418").expect("Failed to send request"); 35 | println!("{}", response.status()); 36 | for header in response.headers().iter() { 37 | println!("{}: {}", header.name(), header.value_string()); 38 | } 39 | let mut buf = String::new(); 40 | response.read_to_string(&mut buf).expect( 41 | "Failed to read response", 42 | ); 43 | println!("{}", buf); 44 | copy(&mut response, &mut stdout()).expect("Failed to read response"); 45 | 46 | let client = reqwest::Client::new(); 47 | let mut params = HashMap::new(); 48 | params.insert("name", "Sir Lancelot"); 49 | params.insert("quest", "to seek the Holy Grail"); 50 | params.insert("favorite_colour", "blue"); 51 | let mut response = client 52 | .post("https://httpbin.org/post") 53 | .form(¶ms) 54 | .send() 55 | .expect("Failed to send request"); 56 | let mut buf = String::new(); 57 | response.read_to_string(&mut buf).expect( 58 | "Failed to read response", 59 | ); 60 | println!("{}", buf); 61 | 62 | let mut response = client 63 | .request(reqwest::Method::Put, "https://httpbin.org/put") 64 | .json(¶ms) 65 | .send() 66 | .expect("Failed to send request"); 67 | let mut buf = String::new(); 68 | response.read_to_string(&mut buf).expect( 69 | "Failed to read response", 70 | ); 71 | println!("{}", buf); 72 | 73 | let response = client 74 | .get("https://httpbin.org/basic-auth/user/passwd") 75 | .send() 76 | .expect("Failed to send request"); 77 | println!("{}", response.status()); 78 | 79 | let credentials = Basic { 80 | username: "user".to_string(), 81 | password: Some("passwd".to_string()), 82 | }; 83 | let response = client 84 | .get("https://httpbin.org/basic-auth/user/passwd") 85 | .header(Authorization(credentials)) 86 | .send() 87 | .expect("Failed to send request"); 88 | println!("{}", response.status()); 89 | 90 | let mut response = client 91 | .get("http://pokeapi.co/api/v2/pokemon/111") 92 | .send() 93 | .expect("Failed to send request"); 94 | if let Ok(pokemon) = response.json::() { 95 | println!("{:#?}", pokemon); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /vol2/src/bin/day21.rs: -------------------------------------------------------------------------------- 1 | extern crate app_dirs; 2 | extern crate preferences; 3 | 4 | #[macro_use] 5 | extern crate serde_derive; 6 | 7 | use app_dirs::{AppDataType, AppInfo, app_dir, app_root, get_app_root}; 8 | use preferences::Preferences; 9 | use std::path::PathBuf; 10 | 11 | const APP_INFO: AppInfo = AppInfo { 12 | name: "24daysofrust", 13 | author: "Zbigniew Siciarz", 14 | }; 15 | 16 | #[derive(Serialize, Deserialize, Debug, Default)] 17 | struct GameConfig { 18 | save_dir: Option, 19 | autosave: bool, 20 | fov: f32, 21 | render_distance: u32, 22 | } 23 | 24 | fn main() { 25 | println!("24 Days of Rust vol. 2 - app_dirs"); 26 | println!("{:?}", get_app_root(AppDataType::UserConfig, &APP_INFO)); 27 | println!("{:?}", app_root(AppDataType::UserConfig, &APP_INFO)); 28 | let save_dir = app_dir(AppDataType::UserData, &APP_INFO, "game/saves") 29 | .expect("Couldn't create directory for game saves"); 30 | println!("{}", save_dir.display()); 31 | let mut config = match GameConfig::load(&APP_INFO, "game_config") { 32 | Ok(cfg) => cfg, 33 | Err(_) => GameConfig::default(), 34 | }; 35 | println!("{:?}", config); 36 | config.save_dir = Some(save_dir); 37 | config.autosave = true; 38 | config.save(&APP_INFO, "game_config").expect( 39 | "Failed to save game config", 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /vol2/src/bin/day22.rs: -------------------------------------------------------------------------------- 1 | extern crate dotenv; 2 | extern crate lettre; 3 | extern crate uuid; 4 | 5 | use std::env; 6 | use lettre::email::{EmailBuilder, SendableEmail}; 7 | use lettre::transport::EmailTransport; 8 | use lettre::transport::smtp; 9 | 10 | struct Report { 11 | contents: String, 12 | recipient: String, 13 | } 14 | 15 | impl SendableEmail for Report { 16 | fn from_address(&self) -> String { 17 | "complicated.report.system@gmail.com".to_string() 18 | } 19 | 20 | fn to_addresses(&self) -> Vec { 21 | vec![self.recipient.clone()] 22 | } 23 | 24 | fn message(&self) -> String { 25 | format!("\nHere's the report you asked for:\n\n{}", self.contents) 26 | } 27 | 28 | fn message_id(&self) -> String { 29 | uuid::Uuid::new_v4().simple().to_string() 30 | } 31 | } 32 | 33 | fn main() { 34 | println!("24 Days of Rust vol. 2 - lettre"); 35 | dotenv::dotenv().ok(); 36 | let email = EmailBuilder::new() 37 | .from("zbigniew.siciarz@goldenline.pl") 38 | .to("zbigniew.siciarz@goldenline.pl") 39 | .subject("Hello Rust!") 40 | .body("Hello Rust!") 41 | .build() 42 | .expect("Failed to build email message"); 43 | println!("{:?}", email); 44 | 45 | let mut transport = smtp::SmtpTransportBuilder::localhost() 46 | .expect("Failed to create transport") 47 | .build(); 48 | println!("{:?}", transport.send(email.clone())); 49 | 50 | let mut transport = smtp::SmtpTransportBuilder::new(("smtp.gmail.com", smtp::SUBMISSION_PORT)) 51 | .expect("Failed to create transport") 52 | .credentials(&env::var("GMAIL_USERNAME").unwrap_or("user".to_string())[..], 53 | &env::var("GMAIL_PASSWORD").unwrap_or("password".to_string())[..]) 54 | .build(); 55 | println!("{:?}", transport.send(email)); 56 | 57 | let report = Report { 58 | contents: "Some very important report".to_string(), 59 | // recipient: "zbigniew@siciarz.net".to_string(), 60 | recipient: "zbigniew.siciarz@goldenline.pl".to_string(), 61 | }; 62 | println!("{:?}", transport.send(report)); 63 | } 64 | -------------------------------------------------------------------------------- /vol2/src/bin/day3.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | 5 | extern crate num_cpus; 6 | extern crate rand; 7 | extern crate rayon; 8 | 9 | use rayon::prelude::*; 10 | 11 | fn monte_carlo_pi(points: usize) -> f64 { 12 | let within_circle = (0..points) 13 | .filter_map(|_| { 14 | let x = rand::random::() * 2f64 - 1f64; 15 | let y = rand::random::() * 2f64 - 1f64; 16 | if x * x + y * y <= 1f64 { Some(1) } else { None } 17 | }) 18 | .count(); 19 | 4f64 * within_circle as f64 / points as f64 20 | } 21 | 22 | fn parallel_monte_carlo_pi(points: usize) -> f64 { 23 | let within_circle = (0..points) 24 | .into_par_iter() 25 | .filter_map(|_| { 26 | let x = rand::random::() * 2f64 - 1f64; 27 | let y = rand::random::() * 2f64 - 1f64; 28 | if x * x + y * y <= 1f64 { Some(1) } else { None } 29 | }) 30 | .count(); 31 | 4f64 * within_circle as f64 / points as f64 32 | } 33 | 34 | fn semicircle(x: f64) -> f64 { 35 | (1f64 - x * x).sqrt() 36 | } 37 | 38 | fn integrate(f: F, points: usize) -> f64 39 | where 40 | F: Fn(f64) -> f64, 41 | { 42 | let delta = 1f64 / points as f64; 43 | (0..points) 44 | .map(|i| { 45 | let a = i as f64 * delta; 46 | delta * (f(a) + f(a + delta)) / 2f64 47 | }) 48 | .sum() 49 | } 50 | 51 | fn parallel_integrate(f: F, points: usize) -> f64 52 | where 53 | F: Fn(f64) -> f64 + Sync, 54 | { 55 | let delta = 1f64 / points as f64; 56 | (0..points) 57 | .into_par_iter() 58 | .map(|i| { 59 | let a = i as f64 * delta; 60 | delta * (f(a) + f(a + delta)) / 2f64 61 | }) 62 | .sum() 63 | } 64 | 65 | fn main() { 66 | println!("24 Days of Rust vol. 2 - rayon"); 67 | println!("This machine has {} CPUs", num_cpus::get()); 68 | println!("sequential: {}", 4f64 * integrate(semicircle, 10_000_000)); 69 | println!( 70 | "parallel: {}", 71 | 4f64 * parallel_integrate(semicircle, 10_000_000) 72 | ); 73 | println!("sequential MC: {}", monte_carlo_pi(1_000_000)); 74 | println!("parallel MC: {}", parallel_monte_carlo_pi(1_000_000)); 75 | } 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | use super::{semicircle, integrate, parallel_integrate, monte_carlo_pi, parallel_monte_carlo_pi}; 80 | use test::Bencher; 81 | 82 | #[bench] 83 | fn bench_sequential_integrate(b: &mut Bencher) { 84 | b.iter(|| integrate(semicircle, 100_000)) 85 | } 86 | 87 | #[bench] 88 | fn bench_parallel_integrate(b: &mut Bencher) { 89 | b.iter(|| parallel_integrate(semicircle, 100_000)) 90 | } 91 | 92 | #[bench] 93 | fn bench_sequential_monte_carlo(b: &mut Bencher) { 94 | b.iter(|| monte_carlo_pi(100_000)) 95 | } 96 | 97 | #[bench] 98 | fn bench_parallel_monte_carlo(b: &mut Bencher) { 99 | b.iter(|| parallel_monte_carlo_pi(100_000)) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /vol2/src/bin/day4.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate slog; 3 | extern crate slog_json; 4 | extern crate slog_term; 5 | extern crate time; 6 | 7 | use std::fs::File; 8 | use slog::Drain; 9 | 10 | #[allow(dead_code)] 11 | struct User { 12 | username: String, 13 | logger: slog::Logger, 14 | } 15 | 16 | type Waldo = String; 17 | type DatabaseError = String; 18 | 19 | impl User { 20 | fn new(username: &str, logger: &slog::Logger) -> Self { 21 | User { 22 | username: username.to_string(), 23 | logger: logger.new(o!("username" => username.to_string())), 24 | } 25 | } 26 | 27 | fn sign_in(&self) { 28 | info!(self.logger, "User signed in"); 29 | } 30 | 31 | fn find_waldo(&self) -> Option { 32 | match read_database(&self.username) { 33 | Ok(waldo) => { 34 | info!(self.logger, "Found Waldo"); 35 | Some(waldo) 36 | } 37 | Err(error) => { 38 | error!(self.logger, "Failed to find Waldo"; "error" => error); 39 | None 40 | } 41 | } 42 | } 43 | } 44 | 45 | fn read_database(username: &str) -> Result { 46 | Err(format!("{}: Not this time", username)) 47 | } 48 | 49 | fn main() { 50 | println!("24 Days of Rust vol. 2 - slog"); 51 | let decorator = slog_term::PlainSyncDecorator::new(std::io::stderr()); 52 | let drain = slog_term::FullFormat::new(decorator).build().fuse(); 53 | let root_logger = slog::Logger::root(drain, o!("version" => "0.5")); 54 | info!(root_logger, "Application started"; 55 | "started_at" => format!("{}", time::now().rfc3339())); 56 | let user = User::new("zbyszek", &root_logger); 57 | user.sign_in(); 58 | let _ = user.find_waldo(); 59 | 60 | let console_drain = slog_term::FullFormat::new( 61 | slog_term::PlainSyncDecorator::new(std::io::stdout()), 62 | ).build() 63 | .fuse(); 64 | let file = File::create("app.log").expect("Couldn't open log file"); 65 | let file_drain = slog_term::FullFormat::new(slog_term::PlainSyncDecorator::new(file)) 66 | .build() 67 | .fuse(); 68 | let logger = slog::Logger::root(slog::Duplicate::new(console_drain, file_drain).fuse(), o!()); 69 | warn!(logger, "not_enough_resources"; "resource" => "cat pictures"); 70 | } 71 | -------------------------------------------------------------------------------- /vol2/src/bin/day5.rs: -------------------------------------------------------------------------------- 1 | extern crate dotenv; 2 | // extern crate envy; 3 | 4 | #[macro_use] 5 | extern crate serde_derive; 6 | 7 | use std::env; 8 | 9 | #[derive(Deserialize, Debug)] 10 | struct Environment { 11 | lang: String, 12 | } 13 | 14 | #[derive(Deserialize, Debug)] 15 | struct MailerConfig { 16 | email_backend: String, 17 | email_from: String, 18 | } 19 | 20 | fn main() { 21 | println!("24 Days of Rust, volume 2 - environment"); 22 | match env::var("LANG") { 23 | Ok(lang) => println!("Language code: {}", lang), 24 | Err(e) => println!("Couldn't read LANG ({})", e), 25 | }; 26 | 27 | // match envy::from_env::() { 28 | // Ok(environment) => println!("Language code: {}", environment.lang), 29 | // Err(e) => println!("Couldn't read LANG ({})", e), 30 | // }; 31 | 32 | dotenv::dotenv().expect("Failed to read .env file"); 33 | println!( 34 | "Email backend: {}", 35 | env::var("EMAIL_BACKEND").expect("EMAIL_BACKEND not found") 36 | ); 37 | 38 | // match envy::from_env::() { 39 | // Ok(config) => println!("{:?}", config), 40 | // Err(e) => println!("Couldn't read mailer config ({})", e), 41 | // }; 42 | } 43 | -------------------------------------------------------------------------------- /vol2/src/bin/day6.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate derive_builder; 3 | 4 | #[derive(Clone, Debug)] 5 | struct Resolution { 6 | width: u32, 7 | height: u32, 8 | } 9 | 10 | impl Default for Resolution { 11 | fn default() -> Resolution { 12 | Resolution { 13 | width: 1920, 14 | height: 1080, 15 | } 16 | } 17 | } 18 | 19 | #[derive(Debug, Default, Builder)] 20 | #[builder(field(private), setter(into))] 21 | struct GameConfig { 22 | #[builder(default)] 23 | resolution: Resolution, 24 | save_dir: Option, 25 | #[builder(default)] 26 | autosave: bool, 27 | fov: f32, 28 | render_distance: u32, 29 | } 30 | 31 | fn main() { 32 | println!("24 Days of Rust vol. 2 - derive_builder"); 33 | let conf = GameConfigBuilder::default() 34 | .save_dir("saves".to_string()) 35 | .fov(70.0) 36 | .render_distance(1000u32) 37 | .build() 38 | .unwrap(); 39 | println!("{:?}", conf); 40 | } 41 | -------------------------------------------------------------------------------- /vol2/src/bin/day7.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | #![feature(plugin)] 3 | #![plugin(phf_macros)] 4 | 5 | #[macro_use] 6 | extern crate lazy_static; 7 | 8 | #[macro_use] 9 | extern crate phf; 10 | 11 | extern crate test; 12 | 13 | #[derive(Clone, Debug)] 14 | pub struct Color { 15 | r: u8, 16 | g: u8, 17 | b: u8, 18 | } 19 | 20 | mod lookups; 21 | 22 | use lookups::{find_color, find_color_lazy_static, find_color_phf}; 23 | 24 | fn main() { 25 | println!("24 Days of Rust vol. 2 - static"); 26 | println!("{:?}", find_color("black")); 27 | println!("{:?}", find_color_lazy_static("fuchsia")); 28 | println!("{:?}", find_color_phf("ROSE")); 29 | } 30 | 31 | #[cfg(test)] 32 | mod tests { 33 | use super::*; 34 | use test::Bencher; 35 | 36 | #[bench] 37 | fn bench_match_lookup(b: &mut Bencher) { 38 | b.iter(|| find_color("White")) 39 | } 40 | 41 | #[bench] 42 | fn bench_lazy_static_map(b: &mut Bencher) { 43 | b.iter(|| find_color_lazy_static("White")) 44 | } 45 | 46 | #[bench] 47 | fn bench_phf_map(b: &mut Bencher) { 48 | b.iter(|| find_color_phf("White")) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /vol2/src/bin/day8.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate serde_derive; 3 | 4 | extern crate serde_json; 5 | extern crate serde_yaml; 6 | 7 | #[derive(Serialize, Deserialize, Debug, Default)] 8 | struct GameConfig { 9 | save_dir: Option, 10 | autosave: bool, 11 | fov: f32, 12 | render_distance: u32, 13 | } 14 | 15 | #[derive(Deserialize, Debug)] 16 | struct Task { 17 | name: String, 18 | command: String, 19 | } 20 | 21 | #[derive(Deserialize, Debug)] 22 | struct Play { 23 | #[serde(rename = "hosts")] 24 | host_list: String, 25 | tasks: Vec, 26 | } 27 | 28 | type Playbook = Vec; 29 | 30 | fn main() { 31 | println!("24 Days of Rust vol. 2 - serde"); 32 | let config = GameConfig::default(); 33 | let json = serde_json::to_string(&config).expect("Couldn't serialize config"); 34 | println!("{}", json); 35 | let json = serde_json::to_string_pretty(&config).expect("Couldn't serialize config"); 36 | println!("{}", json); 37 | 38 | let yaml = include_str!("../../data/playbook.yml"); 39 | println!("{}", yaml); 40 | let playbook = serde_yaml::from_str::(yaml); 41 | println!("{:?}", playbook); 42 | } 43 | -------------------------------------------------------------------------------- /vol2/src/bin/day9.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_family = "windows")] 2 | extern crate winreg; 3 | 4 | #[cfg(target_family = "windows")] 5 | use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE}; 6 | 7 | #[cfg(not(target_family = "windows"))] 8 | fn main() { 9 | println!("winreg is Windows-only"); 10 | } 11 | 12 | #[cfg(target_family = "windows")] 13 | fn main() { 14 | println!("24 Days of Rust vol. 2 - winreg"); 15 | let hklm = winreg::RegKey::predef(HKEY_LOCAL_MACHINE); 16 | let subkey = 17 | hklm.open_subkey_with_flags(r#"SOFTWARE\Microsoft\Windows NT\CurrentVersion"#, KEY_READ) 18 | .expect("Failed to open subkey"); 19 | let product_name: String = subkey.get_value("ProductName").expect( 20 | "Failed to read product name", 21 | ); 22 | println!("Windows version: {}", product_name); 23 | 24 | let subkey = hklm.open_subkey_with_flags( 25 | r#"Software\Microsoft\Windows\CurrentVersion\Uninstall"#, 26 | KEY_READ, 27 | ).expect("Failed to open subkey"); 28 | for key in subkey.enum_keys() { 29 | let _ = key.and_then(|key| subkey.open_subkey_with_flags(key, KEY_READ)) 30 | .and_then(|program_key| program_key.get_value("DisplayName")) 31 | .and_then(|name: String| { 32 | println!("{}", name); 33 | Ok(()) 34 | }); 35 | } 36 | 37 | let delay = 100u32; // in milliseconds 38 | let hkcu = winreg::RegKey::predef(HKEY_CURRENT_USER); 39 | let subkey = hkcu.open_subkey_with_flags( 40 | r#"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"#, 41 | KEY_WRITE, 42 | ).expect("Failed to open subkey"); 43 | subkey.set_value("ExtendedUIHoverTime", &delay).expect( 44 | "Failed to change thumbnail timeout", 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /vol2/templates/blog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ content|markdown|safe }} 8 | 9 | 10 | -------------------------------------------------------------------------------- /vol2/templates/config.ini: -------------------------------------------------------------------------------- 1 | [system] 2 | {% if config.user != "anonymous" %} 3 | user={{ config.user }} 4 | {% endif %} 5 | 6 | [network] 7 | hostname={{ config.hostname }} 8 | email={{ config.email|replace(from="NAME", to=config.user) }} -------------------------------------------------------------------------------- /vol2/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ title|title }} 6 | 7 | 8 |

{{ title|title }}

9 |

{{ content|wordcount }} words

10 |

{{ content }}

11 | {% if todos|length > 1 %} 12 |
    13 | {% for todo in todos %} 14 |
  • {{ todo }}
  • 15 | {% endfor %} 16 |
17 | {% endif %} 18 | 19 | --------------------------------------------------------------------------------