├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE.md ├── .gitignore ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── HOWTO.md ├── LESSONS_LEARNED.md ├── Makefile ├── README.md ├── ci └── render │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── src │ └── main.rs │ └── templates │ ├── EPISODE_LIST.md │ ├── SHOW_NOTES.md │ ├── TWEET.md │ ├── WEBSITE.md │ ├── YOUTUBE.md │ └── YOUTUBE_TITLE.md ├── episode ├── 0 │ ├── README.md │ ├── episode.yml │ └── thumb.jpg ├── 1 │ ├── README.md │ ├── episode.yml │ ├── greeter │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── thumb.jpg ├── 2 │ ├── README.md │ ├── episode.yml │ ├── python │ │ ├── Python List Comprehensions.ipynb │ │ └── code.py │ ├── rust-list-comprehensions │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── rust-quicksort │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── thumb.jpg ├── 3 │ ├── README.md │ ├── episode.yml │ └── thumb.jpg ├── 4 │ ├── README.md │ ├── code │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── resources │ │ │ ├── font │ │ │ │ ├── DejaVuSerif.ttf │ │ │ │ └── LICENSE │ │ │ └── text │ │ │ │ └── wikipedia_en_keyboard.txt │ │ └── src │ │ │ ├── main.rs │ │ │ └── text │ ├── episode.yml │ └── thumb.jpg ├── 5 │ ├── README.md │ ├── balanced │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── episode.yml │ └── thumb.jpg ├── 6 │ ├── README.md │ ├── balanced │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── episode.yml │ ├── parametrize │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── thumb.jpg ├── 7 │ ├── README.md │ ├── episode.yml │ ├── humandate │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── proptest-regressions │ │ │ └── lib.txt │ │ └── src │ │ │ └── lib.rs │ └── thumb.jpg ├── 8 │ ├── README.md │ ├── episode.yml │ ├── lenrs │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── lenrs │ │ │ └── __init__.py │ │ ├── setup.py │ │ └── src │ │ │ └── lib.rs │ └── thumb.jpg ├── 9 │ ├── README.md │ ├── bench │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── benches │ │ │ └── ep9.rs │ │ └── results │ │ │ └── ep9-violin.svg │ ├── episode.yml │ ├── go │ │ ├── .gitignore │ │ ├── README.md │ │ ├── fixed.go │ │ ├── race.go │ │ └── worker.go │ ├── rust │ │ ├── fixed │ │ │ ├── Cargo.lock │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── main.rs │ │ ├── pascal │ │ │ ├── Cargo.lock │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── main.rs │ │ ├── race │ │ │ ├── Cargo.lock │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── main.rs │ │ └── union │ │ │ ├── Cargo.lock │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ └── main.rs │ ├── text │ │ ├── hamlet_gut.txt │ │ ├── henry_v_gut.txt │ │ ├── macbeth_gut_f.txt │ │ └── romeo_and_juliet_gut.txt │ └── thumb.jpg ├── 10 │ ├── README.md │ ├── checklink │ │ ├── .gitignore │ │ ├── .vscode │ │ │ └── launch.json │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src │ │ │ └── main.rs │ └── episode.yml └── .gitignore ├── logo.png ├── logo.svg └── release └── youtube ├── .gitignore ├── Dockerfile └── main.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: mre 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### What? 2 | 3 | *Describe what we should build.* 4 | 5 | ### Why? 6 | 7 | *Describe why that is a fun idea.* 8 | 9 | ### How? 10 | 11 | *Describe how it can be done and what resources might be helpful.* 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | planned 2 | 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "/Users/mendler/.local/share/virtualenvs/image-builder-sYiYx9aO/bin/python" 3 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # So you like to contribute... 2 | 3 | Found a typo? Have an improvement for the code? Fixed a bug? 4 | Thanks for your contributions! 🌈 5 | 6 | A few notes: 7 | 8 | * If you have an idea for a new episode, simply [open an issue](https://github.com/hello-rust/show/issues). 9 | * If you want to change the code for an episode, please add an entry to the `episode.yml`. 10 | [Here is an example pull request](https://github.com/hello-rust/show/pull/45) and 11 | [here is the edited `episode.yml`](https://github.com/killercup/show/blob/cbf317b76d9aaffee2797eb7dfae99c5394674bb/episode/9/episode.yml#L32-L33). 12 | This way, people don't get confused when they look at the code in the future and we can track the changes over time. 13 | -------------------------------------------------------------------------------- /HOWTO.md: -------------------------------------------------------------------------------- 1 | # Creating an episode 2 | 3 | The following notes are helpful for me when recording a new episode. 4 | Also, I thought it might be interesting to others, who also consider to run 5 | their own show. 6 | 7 | ## Getting started 8 | 9 | * Pick an issue from the [issues 10 | page](https://github.com/hello-rust/show/issues) or create a new one. 11 | * Prefix the issue title with the next show number. 12 | 13 | ## Shooting the video 14 | 15 | * Cleanup SD card of digital camera 16 | * Prepare lighting 17 | * Set-up microphone 18 | * Prepare primary and secondary camera 19 | * Test recording and audio 20 | * Close all unrelated apps 21 | * Hide Bookmarks Toolbar in Firefox (right-click next to address bar and remove 22 | tick) 23 | * Prepare IDE settings 24 | - Set slightly bigger font (VS Code: `editor.fontSize": 15`) 25 | - ~~Enter "zen mode"~~ (doesn't show the terminal) 26 | * Set Mac to *do not disturb mode* 27 | * Deactivate night shift 28 | * Start recording and get cracking! 29 | 30 | ## Shooting the intro 31 | 32 | * Create a teaser intro that shows exactly what to expect as an outcome (show 33 | some code as b-roll, maybe?). 34 | 35 | ## Postproduction 36 | 37 | 1. Cut video 38 | 2. Transitions 39 | 3. Normalize sound 40 | 4. Add music and sound effects 41 | 5. Color Grading 42 | 43 | ## Before publication 44 | 45 | * Create nice title images for the video and the website. 46 | 47 | ## Upload 48 | 49 | When uploading the video, I first set it to "unlisted". That gives me another 50 | opportunity to check the quality and the editing of the final cut. At this 51 | phase, I also add show notes to the video description, adjust the title and add 52 | chapters to the video description. 53 | 54 | ## Publication 55 | 56 | * Upload the code to Github. 57 | * Set YouTube video to "public". 58 | * Announce publication on Twitter and Reddit. 59 | -------------------------------------------------------------------------------- /LESSONS_LEARNED.md: -------------------------------------------------------------------------------- 1 | ### Lessons learned the hard way 2 | 3 | - Always double check the final rendered video for hickups before publishing. 4 | The [video speed controller chrome 5 | plugin](https://chrome.google.com/webstore/detail/video-speed-controller/nffaoalbilbmmfgbnbgppjihopabppdk) 6 | helps a lot with screening. 7 | - Video assets are huuuge. A typical episode takes hundreds of gigabytes of 8 | render files. To edit efficiently, I have to squeeze out every byte from my 9 | tiny disk. In the long run, I will have to invest in better hardware, which I 10 | haven't done in a while because Computers are reasonably fast for most tasks 11 | today - except recording. 12 | - After a video is shot, you can not change the narrative anymore. You'll wake 13 | up in the morning thinking: "oh God, I totally forgot THAT". But the only thing 14 | you can do is add a comment. 15 | Therefore, a script is vital to keep track of things I absolutely 16 | have to say during the episode, but I don't use word-for-word screenwriting. 17 | - Every programming mistake during the recording is very expensive. In post, you 18 | have to decide whether to cut it out or leave it in there. Cutting it out can 19 | be tricky, as you might only discover your mistake minutes later and leaving 20 | it in might confuse or even frustrate your viewers ("why do you waste my 21 | time???"). 22 | - If I have a long block of uncut material in my video editor, it's probably not 23 | because there are no hickups, but because I forgot to edit that. 24 | - Never let the recording equipment get in the way of the narrative. In other 25 | words: shoot with what you have. I wanted a second camera but didn't want to 26 | spend a lot of money on it. So I just use an old webcam for now. The quality 27 | might not be perfect, but it's still better than not having it. I'm also 28 | shooting alone and sometimes I would love to have somebody zoom into a scene 29 | when I explain things. For now I'm simply emulating this effect in my Video 30 | editor using crops or a Ken Burns effect. This will dilute the recording 31 | quality a bit, but it adds so much depth and liveness. 32 | 33 | ### Things I did right from the start 34 | 35 | - Adding features as I go: for example in the beginning I manually formatted the show notes. 36 | Seeing that this took so much time, I wrote a small CLI tool to render it from 37 | a YAML file. Over time, I added more output formats for the same content: 38 | YouTube, Twitter, the `README` and so on. The important part is that I did 39 | that over the course of many episodes instead of doing lots of work up-front. 40 | - From day one I took down all observations and learnings. Many things feel 41 | obvious to me now, but they were sure not when I started out. I'm thankful for 42 | taking notes, because I could never go to a beginner mindset, even if I wanted 43 | to. I want other people to have an easier time when learning how to make their own show. 44 | 45 | ### Consistency 46 | 47 | - Doing a single episode takes me *ages*. I would never have thought it takes so 48 | much time. Here's a rough timetable: 49 | * Topic preparation (2-5 hours) 50 | * Audio/Video setup (30min-1h) 51 | * Recording (1h-5h) 52 | * Writing show notes (30min) 53 | * Editing (10-20h) 54 | * Publication (1h). 55 | * Creating a title image (15min) 56 | * Updating the website (15min) 57 | * Sharing on social media (15min) 58 | * Feedback and questions (15min) 59 | 60 | - It's pretty easy to find excuses for skipping an episode: live gets in the 61 | way, there's more important stuff to do, the content doesn't feel just right 62 | yet, there was a technical problem while recording,... don't fall into that 63 | trap. I try to avoid skipping the schedule at all costs. Consistency is king 64 | and viewers reward it. I treat "Hello Rust!" like a TV show: Even if I know 65 | that no episode will ever be perfect, I still publish at the scheduled time 66 | (which can be pretty stressful). 67 | 68 | ### Resources 69 | 70 | - There's a ton of royalty free music out there, but it's hard to find because 71 | it's not properly categorized or the preview is cumbersome. Right now I use 72 | [freemusicarchive](freemusicarchive.org/), although it's not the most 73 | convenient to use. 74 | 75 | ### Simple tips 76 | 77 | - Metadata is quite important to people: one person suggested to add keywords 78 | to the end of the title to improve discovery on YouTube. That was a great 79 | idea! People rarely search for the show directly, but instead they find it 80 | through keywords. 81 | - Similarly, people were frustrated about the flow of the show: some said 82 | I was rushing over some topics, others thought that it was dragging along. 83 | What I did was adding a lot more links to entry-level topics for onboarding 84 | beginners and adding chapters to the videos so that people could jump to the 85 | interesting bits right away. Similarly, I suggest to watch the videos at 1.5x 86 | the speed if you feel bored. That's a nice variation to live-coding where 87 | you're kind of stuck with one pace. 88 | - Presenting on stage or in front of the camera is a performance art. You need 89 | to show people that you got this and that their time is well-invested. The 90 | worst thing you can do is bore them, so skip the chase and get right to the 91 | point. 92 | 93 | ### Finding your voice 94 | 95 | - Bootstrapping a YouTube channel reminds me a lot of bootstrapping a band: you 96 | learn as you go and your first shows are probably awful. This soul-seeking 97 | takes a while until you find your "inner voice" and your audience. 98 | - It's tempting to arrange the show like your viewers presumably want it to be, 99 | but it's very important that you stay true to yourself. If you want to revamp 100 | the show, just do it. Not everybody will like it, but it's your personal 101 | project and it shouldn't become an additional job 102 | - In the beginning, it was hard to let my personality shine through. I'm usually 103 | a very enthusiastic person, especially when talking tech, but in the videos I 104 | sometimes look dull. Some people even commented that it was "painful to 105 | watch". It took me a while to figure out what was the reason: I was scared to 106 | reveal my clumsy self. What helped me was watching other people and learning 107 | their tricks on how to loosen things up: 108 | 109 | #### B-roll 110 | 111 | [Peter McKinnon](https://www.youtube.com/user/petermckinnon24) is a 112 | charismatic storyteller. That's the level of enthusiasm I'd like to show. 113 | He's using a ton of b-roll to support his narrative. 114 | 115 | #### Background music 116 | 117 | The background music in [Hot Ones](https://www.youtube.com/user/FirstWeFeast) 118 | is top-notch. It really creates a thick atmosphere. 119 | 120 | #### Editing 121 | 122 | * The audio and video editing of Masterclass is second to none. Just watch a 123 | trailer of [Chris Hadfield's masterclass on space 124 | exploration](https://www.masterclass.com/classes/chris-hadfield-teaches-space-exploration) 125 | to get a glimpse of that. 126 | 127 | The gist is, music creates a stronger atmosphere 128 | (pizzicato works good for my clumsy style), b-roll loosens up long 129 | explanations (e.g. showing the website of a project while I talk about it), 130 | camera movements and zooms make the content more lively. Sometimes I wish I 131 | had a dedicated camera-person to make that part easier. A second camera might 132 | be nice to record additional footage. 133 | 134 | I found that not many coding channels think about delivery. Many have great 135 | content but they fall short on the execution part which makes the videos less 136 | immersive and entertaining. 137 | That's why I spent time on learning how to edit. There are tons of nice 138 | resources out there like [How Does an Editor Think and 139 | Feel?](https://www.youtube.com/watch?v=3Q3eITC01Fg) and [This Guy Edits](https://www.youtube.com/channel/UCcPuBEAwuF6XWXkcXJXJwsg). 140 | 141 | 142 | ### Technique 143 | 144 | - Like everything else, video editing is a rabbit hole. From multi-camera setups 145 | to LUTs, there's sooo much to learn and experiment with. But it's also a lot 146 | of fun! It's important, that the main focus is always on the content, though. 147 | - [Lighting](https://www.youtube.com/watch?v=eZ5hpcn6tIM) is also a rabbit hole 148 | and so is audio. 149 | 150 | ### Gear 151 | 152 | - Invest in good gear. If you're serious about this, you will spend a LOT of 153 | time in front of the screen, editing text / cutting videos. It's wise to spend 154 | some money on a good screen, mouse, keyboard, and so on. Unfortunately, it's 155 | a never-ending race to find the perfect gear; and it can get quite 156 | expensive. I suggest you look at what sucks the most and fix that first. 157 | - Rendering is still a time-sink in 2018. Plan in some time for that. I usually 158 | aim to render the master video over night. 159 | 160 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Needed SHELL since I'm using zsh 2 | SHELL := /bin/bash 3 | 4 | episodes = $(wildcard episode/*) 5 | 6 | .PHONY: help 7 | help: ## This help message 8 | @echo -e "$$(grep -hE '^\S+:.*##' $(MAKEFILE_LIST) | sed -e 's/:.*##\s*/:/' -e 's/^\(.\+\):\(.*\)/\\x1b[36m\1\\x1b[m:\2/' | column -c2 -t -s :)" 9 | 10 | .PHONY: $(episodes) 11 | $(episodes): ## Render the given episode (e.g. make episode/1) 12 | cd ci/render && cargo build --release 13 | ci/render/target/release/render $@/episode.yml 14 | mkdir -p ../website/content/$(shell basename $@) 15 | cp $@/meta/index.md ../website/content/$(shell basename $@)/index.md 16 | 17 | .PHONY: %.yt 18 | %.yt: ## Build Youtube image 19 | cd release/youtube && docker build -t hello-rust/release:youtube . 20 | 21 | .PHONY: all 22 | all: $(episodes) ## Build all episodes 23 | $(MAKE) $^ 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Hello Rust Show logo](logo.png) 2 | 3 | [🏠 Homepage](https://corrode.dev/hello-rust/) | [▶️ YouTube](https://www.youtube.com/hellorust) | [❤️ Sponsor](https://github.com/sponsors/mre) | [🐦 Twitter](https://twitter.com/hellorustshow) 4 | 5 | This is the main repository of *Hello Rust*, a coding show by [Matthias 6 | Endler](@mre) about the Rust programming language (https://www.youtube.com/hellorust). 7 | It is targeted towards intermediate Rust programmers, who have already read [the book](https://doc.rust-lang.org/book/) and want to learn advanced patterns and tricks as well as how to write ergonomic code in Rust. 8 | 9 | In this repository, you find the list of previous episodes, the planned shows and the show notes. 10 | 11 | ### How to support me 12 | 13 | As you know, producing content takes a lot of time and effort. On top of that, running a YouTube channel requires getting a lot of expensive hardware (like recording and editing equipment) to achieve somewhat acceptable quality. 14 | This show is free for everybody to watch. If you want it to stay this way, consider donating. 15 | [**Sponsor on Github**](https://github.com/sponsors/mre/) and earn a special place in my heart — forever. ❤️ 16 | 17 | ### List of episodes 18 | 19 | #### Season 1 - 2018 20 | 21 | * [#0: **Humble Beginnings**](/episode/0) - An introduction on what this show is about. [[Video](https://youtu.be/jMJRTjnh_jo)] 22 | * [#1: **Hello Universe**](/episode/1) - default trait, debug trait, builder pattern [[Video](https://youtu.be/STWuPMcwwbw)] 23 | * [#2: **Snakes And Gears**](/episode/2) - iterators, map, filter, EntryAPI, List comprehensions [[Video](https://youtu.be/bS5rtxWd2yQ)] 24 | * [#3: **A Code Review**](/episode/3) - Option, Result, Error handling, URL parsing, external crates [[Video](https://youtu.be/a6KWRvAPsmo)] 25 | * [#4: **Touch Typing Tutor**](/episode/4) - touch typing, application, ggez, event handling, game state, iterators, hacking, live-coding [[Video](https://youtu.be/S0Vubd-C5-o)] 26 | * [#5: **Coding Challenge - Balanced Brackets**](/episode/5) - Into trait, Pattern matching, HashMap, Stack, unreachable! macro, coding puzzle, competitive programming [[Video](https://youtu.be/XcuLHO8z_RA)] 27 | * [#6: **Parameterized Tests, Macros, And Refactoring**](/episode/6) - Test data providers, Parameterized tests, AsRef trait, Macros [[Video](https://youtu.be/XJPci7GI-qg)] 28 | * [#7: **Parsing Dates Using Proptest And Tdd**](/episode/7) - Property testing, Quickcheck, Unit testing, Fuzzy testing, TDD [[Video](https://youtu.be/zb7SD0Jco6g)] 29 | * [#8: **Let'S Write A Python Module!**](/episode/8) - Tutorial, FFI, pyo3, Module, Extension, Python [[Video](https://youtu.be/D9r__qxtRMQ)] 30 | * [#9: **Go Vs Rust - Concurrency And Race Conditions**](/episode/9) - race-conditions, ownership, mutex, concurrency, rayon, golang, rustlang [[Video](https://youtu.be/B5xYBrxVSiE)] 31 | 32 | #### Season 2 😙 33 | 34 | * [#1: **Hitting A Bug In The Rust Compiler - While Writing A Boring Link Checker**](/episode/10) - commandline, url, checking, ice, compiler bug [[Video](https://youtu.be/DArJCR0HDL8)] 35 | 36 | If you have an idea for a future show, [don't hesitate to create a new issue or upvote an existing one](/issues). 37 | 38 | ### Inspiration 39 | 40 | The following YouTube creators greatly inspired me. I appreciate the work that went into these channels. 41 | 42 | * [Ferris Streams Stuff](https://www.youtube.com/channel/UC4mpLlHn0FOekNg05yCnkzQ) 43 | * [Fun Fun Function](https://www.youtube.com/channel/UCO1cgjhGzsSYb1rsB4bFe4Q) 44 | * [Just for func](https://github.com/campoy/justforfunc) 45 | 46 | ### FAQ 47 | 48 | **Q: What development environment do you use?**   49 | **A:** Right now, I use *VSCode* and the *Rust* plugin plus *Rust analyzer*. 50 | I've heard good things about the IntelliJ Rust plugin, though. 51 | Therefore, I might try this setup in the future. 52 | 53 | **Q: What audio/video equipment do you use?** 54 | **A:** Video: Canon 700D. Audio: Rhode NT USB, Takstar SGC 598. 55 | 56 | **Q: What is your post-processing routine?**   57 | **A:** Quite simple. I solely use ~~iMovie~~ ~~Davinci Resolve~~ Final Cut right now. 58 | I'm mostly annoyed by the long rendering times on my MacBook. (5 hours for 30 minutes of video.) 59 | 60 | **Q: What is the name of your color theme?** 61 | **A:** Usually I use 1337 and the [Github Theme](https://github.com/primer/github-vscode-theme), but that can vary per show. 62 | I will try to mention it in the show notes, if it's something else. 63 | 64 | **Q: What font are you using?** 65 | **A:** ~Operator Sans Mono~ [Cascadia Code](https://github.com/microsoft/cascadia-code) as a [Nerd Font](https://github.com/ryanoasis/nerd-fonts) variant 66 | 67 | **Q: How long does it take to create one episode?** 68 | **A:** Around 30 hours. Here's a breakdown of the process: 69 | 70 | * 💪 Preparation time: 5 hours 71 | * 👨🏻‍💻 Creating a prototype for the code I'd like to show: 2-5 hours 72 | * 🌺 Prepare office for recording: 30min 73 | * 🖥 Recording: 2-4 hours 74 | * 🎞 Editing: 12-16 hours 75 | * 🍿 Publication: 2 hours 76 | 77 | My hope is to get faster over time. Especially the editing is still 78 | painful, although I've gotten much better already. Publication could also mostly 79 | be done automatically and I wrote some tooling to help me with that (see `ci` 80 | folder). 81 | 82 | ### Credits 83 | 84 | * Gears from the show intro [designed by Freepik](http://www.freepik.com). 85 | * Ornament from the show intro [designed by Freepik](http://www.freepik.com). 86 | -------------------------------------------------------------------------------- /ci/render/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /ci/render/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "render" 3 | version = "0.2.0" 4 | edition = "2018" 5 | authors = ["Matthias Endler "] 6 | 7 | [dependencies] 8 | serde = { version = "1.0", features = ["derive"] } 9 | serde_yaml = "0.8.13" 10 | tera = "1.3.1" 11 | lazy_static = "1.4.0" 12 | url = "2.1.1" 13 | anyhow = "1.0.31" 14 | -------------------------------------------------------------------------------- /ci/render/src/main.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize}; 2 | use lazy_static::lazy_static; 3 | use anyhow::{Context, Result, anyhow}; 4 | use tera::Tera; 5 | use tera::Context as TeraContext; 6 | 7 | use std::env; 8 | use std::path::{Path}; 9 | use std::io::{ BufWriter, Write}; 10 | use std::fs::{self, File}; 11 | 12 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 13 | struct Episode { 14 | number: u64, 15 | title: String, 16 | id: String, 17 | intro: String, 18 | details: Option, 19 | keywords: Vec, 20 | notes: Vec, 21 | others: Option>, 22 | errata: Option>, 23 | metas: Option>, 24 | licenses: Option>, 25 | chapters: Option>, 26 | } 27 | 28 | lazy_static! { 29 | pub static ref TEMPLATES: Tera = { 30 | match Tera::new("ci/render/templates/**/*") { 31 | Ok(t) => t, 32 | Err(e) => { 33 | println!("Parsing error(s): {}", e); 34 | ::std::process::exit(1); 35 | } 36 | } 37 | }; 38 | } 39 | 40 | pub fn write_to_file>(path: P, content: &str) -> Result<()> { 41 | let path = path.as_ref(); 42 | 43 | let file = 44 | File::create(path).with_context(|| format!("Could not create/open file {:?}", path))?; 45 | let mut file = BufWriter::new(file); 46 | 47 | file.write_all(content.as_bytes()) 48 | .with_context(|| format!("Could not write to file {:?}", path))?; 49 | 50 | Ok(()) 51 | } 52 | 53 | fn main() -> Result<()> { 54 | let args: Vec<_> = env::args().collect(); 55 | if args.len() != 2 { 56 | return Err(anyhow!("Expected exactly on argument: ")); 57 | } 58 | 59 | let content = fs::read_to_string(args[1].clone())?; 60 | let episode: Episode = serde_yaml::from_str(&content)?; 61 | let mut context = TeraContext::new(); 62 | context.insert("episode", &episode); 63 | 64 | let readme = TEMPLATES.render("SHOW_NOTES.md", &context); 65 | write_to_file( 66 | format!("episode/{}/README.md", episode.number), 67 | &readme.context("Cannot render README.md")?, 68 | )?; 69 | 70 | fs::create_dir_all(format!("episode/{}/meta", episode.number))?; 71 | 72 | let readme = TEMPLATES.render("WEBSITE.md", &context); 73 | write_to_file( 74 | format!("episode/{}/meta/index.md", episode.number), 75 | &readme.context("Cannot render WEBSITE.md")?, 76 | )?; 77 | 78 | let youtube = TEMPLATES.render("YOUTUBE.md", &context); 79 | write_to_file( 80 | format!("episode/{}/meta/YOUTUBE.md", episode.number), 81 | &youtube.context("Cannot render YouTube description")?, 82 | )?; 83 | 84 | let youtube_title = TEMPLATES.render("YOUTUBE_TITLE.md", &context); 85 | write_to_file( 86 | format!("episode/{}/meta/YOUTUBE_TITLE.md", episode.number), 87 | &youtube_title.context("Cannot render YouTube title")?, 88 | )?; 89 | 90 | let tweet = TEMPLATES.render("TWEET.md", &context); 91 | write_to_file( 92 | format!("episode/{}/meta/TWEET.md", episode.number), 93 | &tweet.context("Cannot render tweet")?, 94 | )?; 95 | 96 | let slug = TEMPLATES.render("EPISODE_LIST.md", &context); 97 | println!("{}", slug.context("Cannot render episode list entry")?); 98 | Ok(()) 99 | } 100 | -------------------------------------------------------------------------------- /ci/render/templates/EPISODE_LIST.md: -------------------------------------------------------------------------------- 1 | * [#{{ episode.number }}: **{{ episode.title | title }}**](/episode/{{ 2 | episode.number }}) - {{ episode.keywords | join(sep=", ") }} [[Video](https://youtu.be/{{episode.id}})] -------------------------------------------------------------------------------- /ci/render/templates/SHOW_NOTES.md: -------------------------------------------------------------------------------- 1 | # Episode {{ episode.number }} - {{ episode.title | title }} 2 | 3 | [![YouTube video thumbnail](./thumb.jpg)](https://corrode.dev/hello-rust/) 4 | **[▶ Watch now on Youtube!](https://youtu.be/{{episode.id}})** 5 | 6 | {{ episode.intro }} 7 | {%- if episode.details %} 8 | {{ episode.details }} 9 | {%- endif %} 10 | 11 | 12 | If you like to get notified about new episodes, [please subscribe to my channel](https://www.youtube.com/hellorust) 😊. 13 | 14 | Keywords: {{ episode.keywords | join(sep=", ") }} 15 | 16 | ## Things I mentioned during the show 17 | 18 | {% for note in episode.notes -%} 19 | * {{ note }} 20 | {% endfor %} 21 | 22 | {%- if episode.others %} 23 | ## Things I should have mentioned (but forgot) 24 | 25 | {% for other in episode.others -%} 26 | * {{ other }} 27 | {% endfor %} 28 | {% endif -%} 29 | 30 | {%- if episode.errata %} 31 | ## Errata and improvements 32 | 33 | It might come as a surprise to you, but every once in a while *even I* make a mistake. 34 | This section covers all improvements made to the code since the epsiode went live. 35 | For an exhaustive list of all changes to the original code, [go here](https://github.com/hello-rust/show/commits/master/episode/{{ episode.number }}). 36 | Thanks to all contributors! 37 | 38 | {% for error in episode.errata -%} 39 | * {{ error }} 40 | {% endfor -%} 41 | {%- endif -%} 42 | 43 | {% if episode.metas %} 44 | ## Meta 45 | 46 | {% for meta in episode.metas -%} 47 | * {{ meta }} 48 | {% endfor %} 49 | {% endif %} 50 | 51 | {%- if episode.licenses %} 52 | ## Resources and licenses 53 | 54 | {% for license in episode.licenses -%} 55 | * {{ license }} 56 | {% endfor %} 57 | {% endif %} 58 | 59 | ## Support! 60 | 61 | Preparing, recording, and editing an episode takes a substantial amount of time 62 | (around 30 hours total). I do all of this next to my fulltime dayjob. 63 | If you want to show your appreciation and help me keep the content free 64 | for everybody to enjoy, [please consider sponsoring me on 65 | Github](https://github.com/sponsors/mre/) - no matter the amount. ❤️ -------------------------------------------------------------------------------- /ci/render/templates/TWEET.md: -------------------------------------------------------------------------------- 1 | 👋 Episode #{{ episode.number }} of #hellorust is out! 2 | {{ episode.intro | truncate(length=250) }} 3 | 4 | Show notes: https://github.com/hello-rust/show/tree/master/episode/{{ episode.number }} 5 | https://youtu.be/{{episode.id}} -------------------------------------------------------------------------------- /ci/render/templates/WEBSITE.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "{{ episode.title }}" 3 | weight = {{ episode.number }} 4 | 5 | [extra] 6 | number = "{{ episode.number }}" 7 | +++ 8 | 9 | {% raw %}{{{% endraw %}youtube(id="{{episode.id }}") {% raw %}}}{% endraw %} 10 | 11 | ## {{ episode.intro }} 12 | 13 | {%- if episode.details %} 14 | {{ episode.details }} 15 | {%- endif %} 16 | 17 | 18 | 19 | ▶ 21 | Run and edit the code on Gitpod 22 | 23 | ## Keywords 24 | 25 | *{{ episode.keywords | join(sep=", ") }}* 26 | 27 | ## Things I mentioned during the show 28 | 29 | {% for note in episode.notes -%} 30 | * {{ note }} 31 | {% endfor %} 32 | 33 | {%- if episode.others %} 34 | ## Things I should have mentioned (but forgot) 35 | 36 | {% for other in episode.others -%} 37 | * {{ other }} 38 | {% endfor %} 39 | {% endif -%} 40 | 41 | {%- if episode.errata %} 42 | ## Errata and improvements 43 | 44 | It might come as a surprise to you, but every once in a while *even I* make a mistake. This section covers all improvements made to the code since the episode went live. For an exhaustive list of all changes to the original code, [go here](https://github.com/hello-rust/show/commits/master/episode/{{ episode.number }}). Thanks to all contributors! 45 | 46 | {% for error in episode.errata -%} 47 | * {{ error }} 48 | {% endfor -%} 49 | {%- endif -%} 50 | 51 | {% if episode.metas %} 52 | ## Meta 53 | 54 | {% for meta in episode.metas -%} 55 | * {{ meta }} 56 | {% endfor %} 57 | {% endif %} 58 | 59 | {%- if episode.licenses %} 60 | ## Resources and licenses 61 | 62 | {% for license in episode.licenses -%} 63 | * {{ license }} 64 | {% endfor %} 65 | {% endif %} 66 | 67 | ## Support! 68 | 69 | Preparing, recording, and editing an episode takes a substantial amount of time 70 | (around 30 hours total). I do all of this next to my fulltime dayjob. 71 | If you want to show your appreciation and help me keep the content free 72 | for everybody to enjoy, [please consider sponsoring me on 73 | Github](https://github.com/sponsors/mre/) - no matter the amount. ❤️ -------------------------------------------------------------------------------- /ci/render/templates/YOUTUBE.md: -------------------------------------------------------------------------------- 1 | ✅ Subscribe: https://www.youtube.com/c/hellorust 2 | 💖 Sponsor: https://github.com/sponsors/mre 3 | 🏠 Website: https://corrode.dev/hello-rust/ 4 | 👨‍💻️ Github: https://github.com/hello-rust/show 5 | 🐦 Twitter: https://twitter.com/hellorustshow 6 | 7 | {{ episode.intro }} 🤓 8 | 9 | {% if episode.chapters-%} 10 | Chapters 11 | {% for chapter in episode.chapters -%} 12 | {{ chapter }} 13 | {% endfor %} 14 | {% endif -%} 15 | 16 | Important Links: 17 | {% for note in episode.notes -%} 18 | * {{ note }} 19 | {% endfor %} 20 | Missing something? Find even more information in the show notes: 21 | https://github.com/hello-rust/show/tree/master/episode/{{ episode.number }} 22 | 23 | {% if episode.licenses -%} 24 | Licensing 25 | {% for license in episode.licenses -%} 26 | {{ license }} 27 | {% endfor %} 28 | {% endif -%} 29 | 30 | Keywords: {{ episode.keywords | join(sep=", ") }} 31 | 32 | Hello Rust! is a show about the Rust programming language. 33 | My goal is to make systems programming accessible and entertaining for everyone. 34 | -------------------------------------------------------------------------------- /ci/render/templates/YOUTUBE_TITLE.md: -------------------------------------------------------------------------------- 1 | Hello Rust! #{{ episode.number }} - {{ episode.title }} ({{ episode.keywords | slice(end=3) | join(sep=", ") }}) -------------------------------------------------------------------------------- /episode/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | script 3 | */meta -------------------------------------------------------------------------------- /episode/0/README.md: -------------------------------------------------------------------------------- 1 | # Episode 0 - Humble Beginnings 2 | 3 | [![YouTube video thumbnail](./thumb.jpg)](https://corrode.dev/hello-rust/) 4 | **[▶ Watch now on Youtube!](https://youtu.be/jMJRTjnh_jo)** 5 | 6 | This is the very first episode of "Hello Rust!". I'm so excited! 7 | 8 | It is my lighthearted journey to become a fearless, more effective Rust programmer. And you can be part, too! My goal is to address beginner and intermediate Rust questions and show that systems programming can be a lot of fun. In this episode, I talk about the show and how I started with Rust. The show is clearly inspired by "JustForFunc" and "Fun Fun Function". 9 | 10 | 11 | 12 | If you like to get notified about new episodes, [please subscribe to my channel](https://www.youtube.com/hellorust) 😊. 13 | 14 | Keywords: introduction, rustlang, live-coding 15 | 16 | ## Things I mentioned during the show 17 | 18 | * [Steve Klabnik - The History of Rust, 2015](https://www.youtube.com/watch?v=79PSagCD_AY) 19 | * [My Blog, where it all started](http://matthias-endler.de/) 20 | * [Rust Cologne - Our monthly meetup](http://www.meetup.com/de/Rust-Cologne-Bonn/) 21 | 22 | ## Things I should have mentioned (but forgot) 23 | 24 | * I was clearly inspired by the following shows: 25 | * [Just for func](https://www.youtube.com/channel/UC_BzFbxG2za3bp5NRRRXJSw) 26 | * [fun fun function](https://www.youtube.com/channel/UCO1cgjhGzsSYb1rsB4bFe4Q) 27 | 28 | 29 | ## Resources and licenses 30 | 31 | * Wallpaper: [Grumpy Cat](https://www.tineye.com/search/4e3d2800ae44015cce4a5ee5e04b94226cae14ba/) 32 | 33 | 34 | 35 | ## Support! 36 | 37 | Preparing, recording, and editing an episode takes a substantial amount of time 38 | (around 30 hours total). I do all of this next to my fulltime dayjob. 39 | If you want to show your appreciation and help me keep the content free 40 | for everybody to enjoy, [please consider sponsoring me on 41 | Github](https://github.com/sponsors/mre/) - no matter the amount. ❤️ -------------------------------------------------------------------------------- /episode/0/episode.yml: -------------------------------------------------------------------------------- 1 | number: 0 2 | title: Humble Beginnings 3 | id: jMJRTjnh_jo 4 | intro: > 5 | This is the very first episode of "Hello Rust!". I'm so excited! 6 | details: > 7 | It is my lighthearted journey to become a fearless, more effective Rust programmer. And you can be part, too! 8 | My goal is to address beginner and intermediate Rust questions and show that systems programming can be a lot of fun. 9 | In this episode, I talk about the show and how I started with Rust. The show is clearly inspired by "JustForFunc" and "Fun Fun Function". 10 | keywords: 11 | - introduction 12 | - rustlang 13 | - live-coding 14 | notes: 15 | - "[Steve Klabnik - The History of Rust, 2015](https://www.youtube.com/watch?v=79PSagCD_AY)" 16 | - "[My Blog, where it all started](http://matthias-endler.de/)" 17 | - "[Rust Cologne - Our monthly meetup](http://www.meetup.com/de/Rust-Cologne-Bonn/)" 18 | others: 19 | - "I was clearly inspired by the following shows:" 20 | - "[Just for func](https://www.youtube.com/channel/UC_BzFbxG2za3bp5NRRRXJSw)" 21 | - "[fun fun function](https://www.youtube.com/channel/UCO1cgjhGzsSYb1rsB4bFe4Q)" 22 | licenses: 23 | - "Wallpaper: [Grumpy Cat](https://www.tineye.com/search/4e3d2800ae44015cce4a5ee5e04b94226cae14ba/)" -------------------------------------------------------------------------------- /episode/0/thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-rust/show/02d40f1db583c3b6da38c18e313e08bfff6b08e7/episode/0/thumb.jpg -------------------------------------------------------------------------------- /episode/1/README.md: -------------------------------------------------------------------------------- 1 | # Episode 1 - Hello Universe 2 | 3 | [![YouTube video thumbnail](./thumb.jpg)](https://corrode.dev/hello-rust/) 4 | **[▶ Watch now on Youtube!](https://youtu.be/STWuPMcwwbw)** 5 | 6 | This is the very first real coding episode of "Hello Rust!". Today we will write *hello world*, but by leveraging the builder pattern, enums, and traits to support different languages (hence the title "Hello Universe"). 7 | 8 | 9 | 10 | If you like to get notified about new episodes, [please subscribe to my channel](https://www.youtube.com/hellorust) 😊. 11 | 12 | Keywords: default trait, debug trait, builder pattern 13 | 14 | ## Things I mentioned during the show 15 | 16 | * [Kernighan & Ritchie - The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language) 17 | * [Default trait](https://doc.rust-lang.org/std/fmt/trait.Default.html) 18 | * [Debug trait](https://doc.rust-lang.org/std/fmt/trait.Debug.html) 19 | 20 | ## Things I should have mentioned (but forgot) 21 | 22 | * [Test organization using `super::*`](https://doc.rust-lang.org/book/second-edition/ch11-03-test-organization.html) 23 | 24 | 25 | ## Meta 26 | 27 | * Colorscheme: [1337](https://github.com/MarkMichos/1337-Scheme) 28 | 29 | 30 | 31 | ## Support! 32 | 33 | Preparing, recording, and editing an episode takes a substantial amount of time 34 | (around 30 hours total). I do all of this next to my fulltime dayjob. 35 | If you want to show your appreciation and help me keep the content free 36 | for everybody to enjoy, [please consider sponsoring me on 37 | Github](https://github.com/sponsors/mre/) - no matter the amount. ❤️ -------------------------------------------------------------------------------- /episode/1/episode.yml: -------------------------------------------------------------------------------- 1 | number: 1 2 | title: Hello Universe 3 | id: STWuPMcwwbw 4 | intro: > 5 | This is the very first real coding episode of "Hello Rust!". 6 | Today we will write *hello world*, but by leveraging the builder pattern, enums, and traits 7 | to support different languages (hence the title "Hello Universe"). 8 | keywords: 9 | - default trait 10 | - debug trait 11 | - builder pattern 12 | notes: 13 | - "[Kernighan & Ritchie - The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language)" 14 | - "[Default trait](https://doc.rust-lang.org/std/fmt/trait.Default.html)" 15 | - "[Debug trait](https://doc.rust-lang.org/std/fmt/trait.Debug.html)" 16 | others: 17 | - "[Test organization using `super::*`](https://doc.rust-lang.org/book/second-edition/ch11-03-test-organization.html)" 18 | metas: 19 | - "Colorscheme: [1337](https://github.com/MarkMichos/1337-Scheme)" -------------------------------------------------------------------------------- /episode/1/greeter/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "greeter" 3 | version = "0.1.0" 4 | 5 | -------------------------------------------------------------------------------- /episode/1/greeter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "greeter" 3 | version = "0.1.0" 4 | authors = ["Matthias Endler "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /episode/1/greeter/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | #[derive(Debug)] 4 | enum Language { 5 | English, 6 | German, 7 | } 8 | 9 | #[derive(Debug)] 10 | struct Greeter { 11 | language: Language, 12 | } 13 | 14 | impl fmt::Display for Greeter { 15 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 16 | let greeting = match self.language { 17 | Language::English => "Hello", 18 | Language::German => "Hallo", 19 | }; 20 | write!(f, "{} Rust", greeting) 21 | } 22 | } 23 | 24 | impl Greeter { 25 | fn new() -> Greeter { 26 | Greeter { 27 | language: Language::English, 28 | } 29 | } 30 | 31 | fn with_language(mut self, language: Language) -> Greeter { 32 | self.language = language; 33 | self 34 | } 35 | } 36 | 37 | #[cfg(test)] 38 | mod tests { 39 | use super::*; 40 | #[test] 41 | fn it_works() { 42 | let greeter = Greeter::new().with_language(Language::German); 43 | assert_eq!(format!("{}", greeter), "Hallo Rust"); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /episode/1/thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-rust/show/02d40f1db583c3b6da38c18e313e08bfff6b08e7/episode/1/thumb.jpg -------------------------------------------------------------------------------- /episode/10/README.md: -------------------------------------------------------------------------------- 1 | # Episode 10 - Hitting A Bug In The Rust Compiler - While Writing A Boring Link Checker 2 | 3 | [![YouTube video thumbnail](./thumb.jpg)](https://corrode.dev/hello-rust/) 4 | **[▶ Watch now on Youtube!](https://youtu.be/DArJCR0HDL8)** 5 | 6 | Long time no see. Let's break the Rust Compiler while I work on a commandline tool for checking links, okay? The link checker we build is now a standalone project that lives here: https://github.com/lycheeverse/lychee 7 | 8 | Hot off the press, here's a brand new episode of Hello Rust, hosted by yours truly: Matthias Endler. While checking links is not the most exciting topic of all times, let's all enjoy the simple things and be modest in your expections. Oh, we also hit a Rust compiler bug in stable Rust (ICE, as the cool kids call them), which panics the program (fixed in nightly). 9 | 10 | 11 | 12 | If you like to get notified about new episodes, [please subscribe to my channel](https://www.youtube.com/hellorust) 😊. 13 | 14 | Keywords: commandline, url, checking, ice, compiler bug 15 | 16 | ## Things I mentioned during the show 17 | 18 | * lychee link checker: https://github.com/lycheeverse/lychee 19 | * githubrs: https://github.com/github-rs/github-rs 20 | * reqwest: https://github.com/seanmonstar/reqwest 21 | * https://github.com/ptasz3k/md-link-check 22 | * envy: https://github.com/mre/envy 23 | * snippet: https://marketplace.visualstudio.com/items?itemName=vscode-snippet.Snippet 24 | * https://emojifinder.com/ 25 | * Tips for faster compile times: https://endler.dev/2020/rust-compile-times/ 26 | 27 | ## Meta 28 | 29 | * Color scheme: Github Dark 30 | 31 | 32 | ## Resources and licenses 33 | 34 | * Juanitos - Hola Hola Bossa Nova https://www.youtube.com/watch?v=hRq53VCMsao 35 | * Cardenio Modern Font by the amazing Nils Cordes (nilscordes.com) 36 | * Clapping sound by theliongirl10 on freesound https://freesound.org/people/theliongirl10/sounds/411217/ 37 | * Cinematic boom by juskiddink on freesound https://freesound.org/people/juskiddink/sounds/130890/ 38 | * Snap sound https://www.youtube.com/watch?v=lWvTV9Tl5PE 39 | * Der Sommer: Chorus: Ach, Das Ungewitter Naht by MIT Concert Choir https://freemusicarchive.org/music/MIT_Concert_Choir/Haydn_The_Seasons_Die_Jahreszeiten/19_-_Der_Sommer-_Chorus-_Ach_Das_Ungewitter_Naht 40 | * Yay sound: https://freesound.org/people/Reitanna/sounds/323703/ 41 | 42 | 43 | 44 | ## Support! 45 | 46 | Preparing, recording, and editing an episode takes a substantial amount of time 47 | (around 30 hours total). I do all of this next to my fulltime dayjob. 48 | If you want to show your appreciation and help me keep the content free 49 | for everybody to enjoy, [please consider sponsoring me on 50 | Github](https://github.com/sponsors/mre/) - no matter the amount. ❤️ -------------------------------------------------------------------------------- /episode/10/checklink/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /episode/10/checklink/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug executable 'checklink'", 11 | "cargo": { 12 | "args": [ 13 | "build", 14 | "--bin=checklink", 15 | "--package=checklink" 16 | ], 17 | "filter": { 18 | "name": "checklink", 19 | "kind": "bin" 20 | } 21 | }, 22 | "args": [], 23 | "cwd": "${workspaceFolder}" 24 | }, 25 | { 26 | "type": "lldb", 27 | "request": "launch", 28 | "name": "Debug unit tests in executable 'checklink'", 29 | "cargo": { 30 | "args": [ 31 | "test", 32 | "--no-run", 33 | "--bin=checklink", 34 | "--package=checklink" 35 | ], 36 | "filter": { 37 | "name": "checklink", 38 | "kind": "bin" 39 | } 40 | }, 41 | "args": [], 42 | "cwd": "${workspaceFolder}" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /episode/10/checklink/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "checklink" 3 | version = "0.1.0" 4 | authors = ["techblog-bot "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | github-rs = "0.7.0" 11 | serde_json = "1.0.56" 12 | regex = "1.3.9" 13 | reqwest = { version = "0.10", features = ["blocking"] } 14 | pulldown-cmark = "0.7.2" 15 | -------------------------------------------------------------------------------- /episode/10/checklink/README.md: -------------------------------------------------------------------------------- 1 | # Simple link checker 2 | 3 | **This is the source code of episode 10 of "Hello, Rust!". 4 | The project now lives at https://github.com/lycheeverse/lychee.** 5 | 6 | Checks links to Github repositories 7 | like [this](https://github.com/mre/idiomatic-rust) 8 | or normal links like [that](https://endler.dev). 9 | 10 | [This](https://endler.dev/abcd) does not exist. 11 | -------------------------------------------------------------------------------- /episode/10/checklink/src/main.rs: -------------------------------------------------------------------------------- 1 | use github_rs::client::{Executor, Github}; 2 | use github_rs::StatusCode; 3 | use pulldown_cmark::{Event, Parser, Tag}; 4 | use regex::Regex; 5 | use serde_json::Value; 6 | use std::fs; 7 | use std::{env, error::Error}; 8 | 9 | struct Checker { 10 | client: Github, 11 | } 12 | 13 | impl Checker { 14 | /// Creates a new link checker 15 | pub fn new(token: String) -> Self { 16 | let client = Github::new(token).unwrap(); 17 | Checker { client } 18 | } 19 | 20 | fn check_github(&self, owner: String, repo: String) -> bool { 21 | let (_headers, status, _json) = self 22 | .client 23 | .get() 24 | .repos() 25 | .owner(&owner) 26 | .repo(&repo) 27 | .execute::() 28 | .expect("Get failed"); 29 | status == StatusCode::OK 30 | } 31 | 32 | fn check_normal(&self, url: &String) -> bool { 33 | let res = reqwest::blocking::get(url); 34 | if let Ok(res) = res { 35 | res.status().is_success() 36 | } else { 37 | false 38 | } 39 | } 40 | 41 | fn extract_github(&self, url: &String) -> Result<(String, String), Box> { 42 | let re = Regex::new(r"github\.com/(.*?)/(.*)/?")?; 43 | let caps = re.captures(&url).ok_or("Invalid capture")?; 44 | let owner = caps.get(1).ok_or("Cannot capture owner")?; 45 | let repo = caps.get(2).ok_or("Cannot capture repo")?; 46 | Ok((owner.as_str().into(), repo.as_str().into())) 47 | } 48 | 49 | pub fn check(&self, url: &String) -> bool { 50 | match self.extract_github(&url) { 51 | Ok((owner, repo)) => self.check_github(owner, repo), 52 | _ => self.check_normal(&url), 53 | } 54 | } 55 | } 56 | 57 | fn extract_links(md: &str) -> Vec { 58 | let mut links: Vec = Vec::new(); 59 | Parser::new(md).for_each(|event| match event { 60 | Event::Start(Tag::Link(_, link, _)) => links.push(link.into_string()), 61 | Event::Start(Tag::Image(_, link, _)) => links.push(link.into_string()), 62 | _ => (), 63 | }); 64 | 65 | links 66 | } 67 | 68 | fn main() -> Result<(), Box> { 69 | let checker = Checker::new(env::var("GITHUB_TOKEN")?); 70 | let md = fs::read_to_string("README.md")?; 71 | let links: Vec = extract_links(&md); 72 | for link in links { 73 | match checker.check(&link) { 74 | true => println!("✅{}", link), 75 | false => println!("❌{}", link), 76 | } 77 | } 78 | Ok(()) 79 | } 80 | 81 | #[cfg(test)] 82 | mod test { 83 | use super::*; 84 | use std::env; 85 | 86 | #[test] 87 | fn test_is_github() { 88 | let checker = Checker::new("foo".into()); 89 | assert_eq!( 90 | checker 91 | .extract_github(&"https://github.com/mre/idiomatic-rust".into()) 92 | .unwrap(), 93 | ("mre".into(), "idiomatic-rust".into()) 94 | ); 95 | } 96 | 97 | #[test] 98 | fn test_github() { 99 | let checker = Checker::new(env::var("GITHUB_TOKEN").unwrap()); 100 | assert_eq!( 101 | checker.check("https://github.com/mre/idiomatic-rust".into()), 102 | true 103 | ); 104 | } 105 | 106 | #[test] 107 | fn test_github_nonexistent() { 108 | let checker = Checker::new(env::var("GITHUB_TOKEN").unwrap()); 109 | assert_eq!( 110 | checker.check("https://github.com/mre/idiomatic-rust-doesnt-exist-man".into()), 111 | false 112 | ); 113 | } 114 | 115 | #[test] 116 | fn test_non_github() { 117 | let checker = Checker::new(env::var("GITHUB_TOKEN").unwrap()); 118 | let valid = checker.check("https://endler.dev".into()); 119 | assert_eq!(valid, true); 120 | } 121 | 122 | #[test] 123 | fn test_non_github_nonexistent() { 124 | let checker = Checker::new(env::var("GITHUB_TOKEN").unwrap()); 125 | let valid = checker.check("https://endler.dev/abcd".into()); 126 | assert_eq!(valid, false); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /episode/10/episode.yml: -------------------------------------------------------------------------------- 1 | number: 10 2 | title: Hitting A Bug in the Rust Compiler - While Writing A Boring Link Checker 3 | id: DArJCR0HDL8 4 | intro: > 5 | Long time no see. Let's break the Rust Compiler while I work on a 6 | commandline tool for checking links, okay? 7 | The link checker we build is now a standalone project that lives here: 8 | https://github.com/lycheeverse/lychee 9 | details: > 10 | Hot off the press, here's a brand new episode of Hello Rust, hosted by yours 11 | truly: Matthias Endler. 12 | While checking links is not the most exciting topic of all times, let's all 13 | enjoy the simple things and be modest in your expections. 14 | Oh, we also hit a Rust compiler bug in stable Rust (ICE, as the cool kids call them), which 15 | panics the program (fixed in nightly). 16 | 17 | keywords: 18 | - commandline 19 | - url 20 | - checking 21 | - ice 22 | - compiler bug 23 | notes: 24 | - "lychee link checker: https://github.com/lycheeverse/lychee" 25 | - "githubrs: https://github.com/github-rs/github-rs" 26 | - "reqwest: https://github.com/seanmonstar/reqwest" 27 | - "https://github.com/ptasz3k/md-link-check" 28 | - "envy: https://github.com/mre/envy" 29 | - "snippet: https://marketplace.visualstudio.com/items?itemName=vscode-snippet.Snippet" 30 | - "https://emojifinder.com/" 31 | - "Tips for faster compile times: https://endler.dev/2020/rust-compile-times/" 32 | metas: 33 | - "Color scheme: Github Dark" 34 | licenses: 35 | - "Juanitos - Hola Hola Bossa Nova https://www.youtube.com/watch?v=hRq53VCMsao" 36 | - "Cardenio Modern Font by the amazing Nils Cordes (nilscordes.com)" 37 | - "Clapping sound by theliongirl10 on freesound https://freesound.org/people/theliongirl10/sounds/411217/" 38 | - "Cinematic boom by juskiddink on freesound https://freesound.org/people/juskiddink/sounds/130890/" 39 | - "Snap sound https://www.youtube.com/watch?v=lWvTV9Tl5PE" 40 | - "Der Sommer: Chorus: Ach, Das Ungewitter Naht by MIT Concert Choir https://freemusicarchive.org/music/MIT_Concert_Choir/Haydn_The_Seasons_Die_Jahreszeiten/19_-_Der_Sommer-_Chorus-_Ach_Das_Ungewitter_Naht" 41 | - "Yay sound: https://freesound.org/people/Reitanna/sounds/323703/" 42 | chapters: 43 | - "Our code skeleton: 1:09" 44 | - "github-rs Repository API: 2:38" 45 | - "Github token + envy: 4:50" 46 | - "Deserializing the Repo: 7:05" 47 | - "Creating a checker struct: 8:51" 48 | - "Code cleanup: 10:48" 49 | - "Async we should look for alternatives: 13:25" 50 | - "Segfault: 16:03" 51 | - "Pulling in Markdown: 17:15" 52 | - "Fancy emoji: 19:05" 53 | - "Subscribe everyone plz: 20:26" 54 | -------------------------------------------------------------------------------- /episode/2/README.md: -------------------------------------------------------------------------------- 1 | # Episode 2 - Snakes And Gears 2 | 3 | [![YouTube video thumbnail](./thumb.jpg)](https://corrode.dev/hello-rust/) 4 | **[▶ Watch now on Youtube!](https://youtu.be/bS5rtxWd2yQ)** 5 | 6 | Let's talk about Python and Rust. I used to do a lot more Python than I do now, but I still love it for its beauty. 7 | 8 | One of the first things I was missing in Rust when coming from Python were List Comprehensions. It took me a while to figure out, that the Rust way - using iter(), filter(), and map() - actually is a better fit for the language. I want to show you how to port Rust list comprehensions to Rust. 9 | 10 | 11 | 12 | If you like to get notified about new episodes, [please subscribe to my channel](https://www.youtube.com/hellorust) 😊. 13 | 14 | Keywords: iterators, map, filter, Entry API, List comprehensions 15 | 16 | ## Things I mentioned during the show 17 | 18 | * Reading files using the [Rust Cookbook](https://rust-lang-nursery.github.io/rust-cookbook/file/read-write.html#read-lines-of-strings-from-a-file). 19 | * [DevDocs](http://devdocs.io/) is a nice Rust documentation viewer. Also supports a ton of other languages and works offline. 20 | 21 | ## Things I should have mentioned (but forgot) 22 | 23 | * [Iteration patterns for Result & Option](http://xion.io/post/code/rust-iter-patterns.html) 24 | * Experimenting with Python is much cooler with [Jupyter](http://jupyter.org/) (or IPython) notebooks. I've added an example to the code section. 25 | * If you really **need** list comprehensions, you could use [cute](https://crates.io/crates/cute), which is a set of macros that make this syntax possible. 26 | 27 | 28 | ## Meta 29 | 30 | * Colorscheme: [Gruvbox Dark](https://github.com/morhetz/gruvbox) 31 | 32 | 33 | ## Resources and licenses 34 | 35 | * Intro song: Aries Beats - Night Ride (https://www.youtube.com/watch?v=AOvr_57BMZo) 36 | 37 | 38 | 39 | ## Support! 40 | 41 | Preparing, recording, and editing an episode takes a substantial amount of time 42 | (around 30 hours total). I do all of this next to my fulltime dayjob. 43 | If you want to show your appreciation and help me keep the content free 44 | for everybody to enjoy, [please consider sponsoring me on 45 | Github](https://github.com/sponsors/mre/) - no matter the amount. ❤️ -------------------------------------------------------------------------------- /episode/2/episode.yml: -------------------------------------------------------------------------------- 1 | number: 2 2 | title: Snakes and Gears 3 | id: bS5rtxWd2yQ 4 | intro: > 5 | Let's talk about Python and Rust. I used to do a lot more Python than I do now, but I still love it for its beauty. 6 | details: > 7 | One of the first things I was missing in Rust when coming from Python were List Comprehensions. 8 | It took me a while to figure out, that the Rust way - using iter(), filter(), and map() - actually is a better fit for the language. 9 | I want to show you how to port Rust list comprehensions to Rust. 10 | keywords: 11 | - iterators 12 | - map 13 | - filter 14 | - Entry API 15 | - List comprehensions 16 | notes: 17 | - Reading files using the [Rust Cookbook](https://rust-lang-nursery.github.io/rust-cookbook/file/read-write.html#read-lines-of-strings-from-a-file). 18 | - "[DevDocs](http://devdocs.io/) is a nice Rust documentation viewer. Also supports a ton of other languages and works offline." 19 | others: 20 | - "[Iteration patterns for Result & Option](http://xion.io/post/code/rust-iter-patterns.html)" 21 | - Experimenting with Python is much cooler with [Jupyter](http://jupyter.org/) (or IPython) notebooks. I've added an example to the code section. 22 | - If you really **need** list comprehensions, you could use [cute](https://crates.io/crates/cute), which is a set of macros that make this syntax possible. 23 | metas: 24 | - "Colorscheme: [Gruvbox Dark](https://github.com/morhetz/gruvbox)" 25 | licenses: 26 | - "Intro song: Aries Beats - Night Ride (https://www.youtube.com/watch?v=AOvr_57BMZo)" -------------------------------------------------------------------------------- /episode/2/python/Python List Comprehensions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "slideshow": { 8 | "slide_type": "slide" 9 | } 10 | }, 11 | "outputs": [], 12 | "source": [ 13 | "words = []\n", 14 | "for word in open(\"/usr/share/dict/words\"):\n", 15 | " words.append(word)" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": null, 28 | "metadata": { 29 | "slideshow": { 30 | "slide_type": "subslide" 31 | } 32 | }, 33 | "outputs": [], 34 | "source": [ 35 | "words" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": null, 41 | "metadata": { 42 | "slideshow": { 43 | "slide_type": "slide" 44 | } 45 | }, 46 | "outputs": [], 47 | "source": [ 48 | "with open(\"/usr/share/dict/words\") as f:\n", 49 | " words = [word.strip() for word in f]" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "metadata": { 56 | "slideshow": { 57 | "slide_type": "subslide" 58 | } 59 | }, 60 | "outputs": [], 61 | "source": [ 62 | "words" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": null, 68 | "metadata": { 69 | "slideshow": { 70 | "slide_type": "slide" 71 | } 72 | }, 73 | "outputs": [], 74 | "source": [ 75 | "word_lengths = {word: len(word) for word in words}" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": null, 81 | "metadata": { 82 | "slideshow": { 83 | "slide_type": "subslide" 84 | } 85 | }, 86 | "outputs": [], 87 | "source": [ 88 | "word_lengths" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": null, 94 | "metadata": { 95 | "slideshow": { 96 | "slide_type": "subslide" 97 | } 98 | }, 99 | "outputs": [], 100 | "source": [ 101 | "from collections import defaultdict\n", 102 | "same_length = defaultdict(set)\n", 103 | "for word, length in word_lengths.items():\n", 104 | " same_length[length].add(word)" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": null, 110 | "metadata": { 111 | "slideshow": { 112 | "slide_type": "subslide" 113 | } 114 | }, 115 | "outputs": [], 116 | "source": [ 117 | "same_length" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": null, 123 | "metadata": { 124 | "slideshow": { 125 | "slide_type": "slide" 126 | } 127 | }, 128 | "outputs": [], 129 | "source": [ 130 | "[open(f).readlines() for f in set([\"text1\", \"text2\"])]" 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": null, 136 | "metadata": { 137 | "slideshow": { 138 | "slide_type": "subslide" 139 | } 140 | }, 141 | "outputs": [], 142 | "source": [ 143 | "[open(f).readlines() for f in set([\"text1\", \"text2\", \"test3\"])]" 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": null, 149 | "metadata": { 150 | "slideshow": { 151 | "slide_type": "slide" 152 | } 153 | }, 154 | "outputs": [], 155 | "source": [ 156 | "def quicksort(values):\n", 157 | " if not values:\n", 158 | " return []\n", 159 | " else:\n", 160 | " pivot = values[0]\n", 161 | " less = [x for x in values if x < pivot]\n", 162 | " more = [x for x in values[1:] if x >= pivot]\n", 163 | " return quicksort(less) + [pivot] + quicksort(more)" 164 | ] 165 | }, 166 | { 167 | "cell_type": "code", 168 | "execution_count": null, 169 | "metadata": { 170 | "slideshow": { 171 | "slide_type": "subslide" 172 | } 173 | }, 174 | "outputs": [], 175 | "source": [ 176 | "quicksort([5,7,1,3,7,2,2,3])" 177 | ] 178 | } 179 | ], 180 | "metadata": { 181 | "celltoolbar": "Slideshow", 182 | "kernelspec": { 183 | "display_name": "Python 3", 184 | "language": "python", 185 | "name": "python3" 186 | }, 187 | "language_info": { 188 | "codemirror_mode": { 189 | "name": "ipython", 190 | "version": 3 191 | }, 192 | "file_extension": ".py", 193 | "mimetype": "text/x-python", 194 | "name": "python", 195 | "nbconvert_exporter": "python", 196 | "pygments_lexer": "ipython3", 197 | "version": "3.6.4" 198 | } 199 | }, 200 | "nbformat": 4, 201 | "nbformat_minor": 2 202 | } 203 | -------------------------------------------------------------------------------- /episode/2/python/code.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | words = [word for word in open("/usr/share/dict/words")] 4 | 5 | word_lengths = defaultdict(set) 6 | for word in words: 7 | word_lengths[len(word)].add(word) 8 | print(word_lengths) 9 | 10 | 11 | def quicksort(items): 12 | if not items: 13 | return [] 14 | pivot = items[0] 15 | smaller = [i for i in items if i < pivot] 16 | bigger = [i for i in items[1:] if i >= pivot] 17 | return quicksort(smaller) + [pivot] + quicksort(bigger) 18 | 19 | 20 | print(quicksort([6, 2, 7, 2, 3, 4])) 21 | -------------------------------------------------------------------------------- /episode/2/rust-list-comprehensions/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "rust" 3 | version = "0.1.0" 4 | 5 | -------------------------------------------------------------------------------- /episode/2/rust-list-comprehensions/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust" 3 | version = "0.1.0" 4 | authors = ["Matthias Endler "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /episode/2/rust-list-comprehensions/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{Write, BufReader, BufRead}; 3 | use std::collections::{HashMap, HashSet}; 4 | 5 | fn main() { 6 | let numbers: Vec = (1..1000).collect(); 7 | println!("{:?}", numbers); 8 | 9 | let path = "/usr/share/dict/words"; 10 | let input = File::open(path).unwrap(); 11 | let buffered = BufReader::new(input); 12 | 13 | let words: Vec = buffered.lines().filter_map(Result::ok).collect(); 14 | 15 | let mut word_length: HashMap = HashMap::new(); 16 | for word in words { 17 | word_length.entry(word.len()).or_insert(HashSet::new()).insert(word); 18 | } 19 | 20 | println!("{:?}", word_length[&3]); 21 | } 22 | -------------------------------------------------------------------------------- /episode/2/rust-quicksort/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "rust-quicksort" 3 | version = "0.1.0" 4 | 5 | -------------------------------------------------------------------------------- /episode/2/rust-quicksort/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-quicksort" 3 | version = "0.1.0" 4 | authors = ["Matthias Endler "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /episode/2/rust-quicksort/src/main.rs: -------------------------------------------------------------------------------- 1 | fn quicksort(items: Vec) -> Vec { 2 | let pivot = match items.get(0) { 3 | Some(i) => i, 4 | _ => return vec![], 5 | }; 6 | let less = items.iter().map(|&i| i).filter(|i| i < pivot).collect(); 7 | let more = items 8 | .iter() 9 | .skip(1) 10 | .map(|&i| i) 11 | .filter(|i| i >= pivot) 12 | .collect(); 13 | [quicksort(less), vec![*pivot], quicksort(more)].concat() 14 | } 15 | 16 | fn main() { 17 | println!("{:?}", quicksort(vec![6, 2, 7, 2, 3, 4])); 18 | } 19 | -------------------------------------------------------------------------------- /episode/2/thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-rust/show/02d40f1db583c3b6da38c18e313e08bfff6b08e7/episode/2/thumb.jpg -------------------------------------------------------------------------------- /episode/3/README.md: -------------------------------------------------------------------------------- 1 | # Episode 3 - A Code Review 2 | 3 | [![YouTube video thumbnail](./thumb.jpg)](https://corrode.dev/hello-rust/) 4 | **[▶ Watch now on Youtube!](https://youtu.be/a6KWRvAPsmo)** 5 | 6 | Today we will do our first code review. 7 | 8 | I picked a library from my colleague [Luca Pizzamiglio](https://github.com/pizzamig/) called [repoctl](https://github.com/pizzamig/repoctl), which is a tool for handling FreeBSD package files in Rust. The goal is to improve the codebase and make it more robust and idiomatic. 9 | 10 | 11 | 12 | If you like to get notified about new episodes, [please subscribe to my channel](https://www.youtube.com/hellorust) 😊. 13 | 14 | Keywords: Option, Result, Error handling, URL parsing, external crates 15 | 16 | ## Things I mentioned during the show 17 | 18 | * [repoctl main repository](https://github.com/pizzamig/repoctl) 19 | * [repoctl pull request from the episode](https://github.com/pizzamig/repoctl/pull/1) 20 | * [autoconf documentation](https://www.gnu.org/software/autoconf/autoconf.html) 21 | * [UCL - the Universal Configuration Language](https://github.com/vstakhov/libucl) 22 | * [Rust bindings for UCL](https://github.com/hauleth/ucl-rs) 23 | * [URL crate](https://crates.io/crates/url) 24 | * [Rust RFC - Clarify and streamline paths and visibility](https://github.com/rust-lang/rust/issues/44660) 25 | 26 | ## Things I should have mentioned (but forgot) 27 | 28 | * You should definitely create a pull request to repoctl! Luca and me will support you if we can. Below are some ideas. 29 | * Replace things like `fuf` and `fuf2` in the tests with a data provider 30 | * Use `unwrap_or_default()` where useful 31 | * Run [clippy](https://github.com/rust-lang-nursery/rust-clippy) on the project and fix all code smells. 32 | * Somebody should implement [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) for [`Repository`](https://github.com/pizzamig/repoctl/blob/master/src/repository/mod.rs#L9). 33 | 34 | 35 | ## Resources and licenses 36 | 37 | * [Elevator sound](https://freesound.org/people/omarie/sounds/382447/) 38 | * [Comic whistle sound](https://freesound.org/people/InspectorJ/sounds/410803/) 39 | * [Swoosh sound](https://freesound.org/people/martian/sounds/19312/) 40 | 41 | 42 | 43 | ## Support! 44 | 45 | Preparing, recording, and editing an episode takes a substantial amount of time 46 | (around 30 hours total). I do all of this next to my fulltime dayjob. 47 | If you want to show your appreciation and help me keep the content free 48 | for everybody to enjoy, [please consider sponsoring me on 49 | Github](https://github.com/sponsors/mre/) - no matter the amount. ❤️ -------------------------------------------------------------------------------- /episode/3/episode.yml: -------------------------------------------------------------------------------- 1 | number: 3 2 | title: A Code Review 3 | id: a6KWRvAPsmo 4 | intro: > 5 | Today we will do our first code review. 6 | details: > 7 | I picked a library from my colleague [Luca Pizzamiglio](https://github.com/pizzamig/) called [repoctl](https://github.com/pizzamig/repoctl), which is a tool for handling FreeBSD package files in Rust. 8 | The goal is to improve the codebase and make it more robust and idiomatic. 9 | keywords: 10 | - Option 11 | - Result 12 | - Error handling 13 | - URL parsing 14 | - external crates 15 | notes: 16 | - "[repoctl main repository](https://github.com/pizzamig/repoctl)" 17 | - "[repoctl pull request from the episode](https://github.com/pizzamig/repoctl/pull/1)" 18 | - "[autoconf documentation](https://www.gnu.org/software/autoconf/autoconf.html)" 19 | - "[UCL - the Universal Configuration Language](https://github.com/vstakhov/libucl)" 20 | - "[Rust bindings for UCL](https://github.com/hauleth/ucl-rs)" 21 | - "[URL crate](https://crates.io/crates/url)" 22 | - "[Rust RFC - Clarify and streamline paths and visibility](https://github.com/rust-lang/rust/issues/44660) " 23 | others: 24 | - You should definitely create a pull request to repoctl! Luca and me will support you if we can. Below are some ideas. 25 | - Replace things like `fuf` and `fuf2` in the tests with a data provider 26 | - Use `unwrap_or_default()` where useful 27 | - Run [clippy](https://github.com/rust-lang-nursery/rust-clippy) on the project and fix all code smells. 28 | - Somebody should implement [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) for [`Repository`](https://github.com/pizzamig/repoctl/blob/master/src/repository/mod.rs#L9). 29 | licenses: 30 | - "[Elevator sound](https://freesound.org/people/omarie/sounds/382447/)" 31 | - "[Comic whistle sound](https://freesound.org/people/InspectorJ/sounds/410803/)" 32 | - "[Swoosh sound](https://freesound.org/people/martian/sounds/19312/)" -------------------------------------------------------------------------------- /episode/3/thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-rust/show/02d40f1db583c3b6da38c18e313e08bfff6b08e7/episode/3/thumb.jpg -------------------------------------------------------------------------------- /episode/4/README.md: -------------------------------------------------------------------------------- 1 | # Episode 4 - Touch Typing Tutor 2 | 3 | [![YouTube video thumbnail](./thumb.jpg)](https://corrode.dev/hello-rust/) 4 | **[▶ Watch now on Youtube!](https://youtu.be/S0Vubd-C5-o)** 5 | 6 | I hack on a small application that will help me get better at touch typing. 7 | 8 | Not a tutorial, but just a fun hacking session to build a touch typing application using ggez - a game framework written in Rust. 9 | 10 | 11 | 12 | If you like to get notified about new episodes, [please subscribe to my channel](https://www.youtube.com/hellorust) 😊. 13 | 14 | Keywords: touch typing, application, ggez, event handling, game state, iterators, hacking, live-coding 15 | 16 | ## Things I mentioned during the show 17 | 18 | * [ggez on Github](https://github.com/ggez/ggez) 19 | * [ggez homepage](http://ggez.rs/) 20 | * [Type racer](http://typeracer.com/) 21 | * [keybr](http://keybr.com/) 22 | 23 | ## Meta 24 | 25 | * My Keyboard: [Durgod Taurus K320](https://www.aliexpress.com/item/durgod-87-taurus-k320-mechanical-keyboard-using-cherry-mx-switches-pbt-doubleshot-keycaps-brown-blue-black/32845509908.html) (Cherry MX Brown keys + O-Rings) 26 | 27 | 28 | ## Resources and licenses 29 | 30 | * I've used sound effects from [free sound pack by Setuniman](https://freesound.org/people/Setuniman/packs/8199/) 31 | * [Curious by Setuniman](https://freesound.org/people/Setuniman/sounds/154907/) available at https://freesound.org/s/154907/ 32 | * Plop sound by [HerbertBoland](https://freesound.org/people/HerbertBoland/sounds/33369/) 33 | 34 | 35 | 36 | ## Support! 37 | 38 | Preparing, recording, and editing an episode takes a substantial amount of time 39 | (around 30 hours total). I do all of this next to my fulltime dayjob. 40 | If you want to show your appreciation and help me keep the content free 41 | for everybody to enjoy, [please consider sponsoring me on 42 | Github](https://github.com/sponsors/mre/) - no matter the amount. ❤️ -------------------------------------------------------------------------------- /episode/4/code/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "code" 3 | version = "0.1.0" 4 | authors = ["Matthias Endler "] 5 | 6 | [dependencies] 7 | ggez = "0.4.2" 8 | -------------------------------------------------------------------------------- /episode/4/code/resources/font/DejaVuSerif.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-rust/show/02d40f1db583c3b6da38c18e313e08bfff6b08e7/episode/4/code/resources/font/DejaVuSerif.ttf -------------------------------------------------------------------------------- /episode/4/code/resources/font/LICENSE: -------------------------------------------------------------------------------- 1 | Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. 2 | Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below) 3 | 4 | 5 | Bitstream Vera Fonts Copyright 6 | ------------------------------ 7 | 8 | Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is 9 | a trademark of Bitstream, Inc. 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of the fonts accompanying this license ("Fonts") and associated 13 | documentation files (the "Font Software"), to reproduce and distribute the 14 | Font Software, including without limitation the rights to use, copy, merge, 15 | publish, distribute, and/or sell copies of the Font Software, and to permit 16 | persons to whom the Font Software is furnished to do so, subject to the 17 | following conditions: 18 | 19 | The above copyright and trademark notices and this permission notice shall 20 | be included in all copies of one or more of the Font Software typefaces. 21 | 22 | The Font Software may be modified, altered, or added to, and in particular 23 | the designs of glyphs or characters in the Fonts may be modified and 24 | additional glyphs or characters may be added to the Fonts, only if the fonts 25 | are renamed to names not containing either the words "Bitstream" or the word 26 | "Vera". 27 | 28 | This License becomes null and void to the extent applicable to Fonts or Font 29 | Software that has been modified and is distributed under the "Bitstream 30 | Vera" names. 31 | 32 | The Font Software may be sold as part of a larger software package but no 33 | copy of one or more of the Font Software typefaces may be sold by itself. 34 | 35 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 36 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, 37 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, 38 | TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME 39 | FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING 40 | ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, 41 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 42 | THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE 43 | FONT SOFTWARE. 44 | 45 | Except as contained in this notice, the names of Gnome, the Gnome 46 | Foundation, and Bitstream Inc., shall not be used in advertising or 47 | otherwise to promote the sale, use or other dealings in this Font Software 48 | without prior written authorization from the Gnome Foundation or Bitstream 49 | Inc., respectively. For further information, contact: fonts at gnome dot 50 | org. 51 | 52 | Arev Fonts Copyright 53 | ------------------------------ 54 | 55 | Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. 56 | 57 | Permission is hereby granted, free of charge, to any person obtaining 58 | a copy of the fonts accompanying this license ("Fonts") and 59 | associated documentation files (the "Font Software"), to reproduce 60 | and distribute the modifications to the Bitstream Vera Font Software, 61 | including without limitation the rights to use, copy, merge, publish, 62 | distribute, and/or sell copies of the Font Software, and to permit 63 | persons to whom the Font Software is furnished to do so, subject to 64 | the following conditions: 65 | 66 | The above copyright and trademark notices and this permission notice 67 | shall be included in all copies of one or more of the Font Software 68 | typefaces. 69 | 70 | The Font Software may be modified, altered, or added to, and in 71 | particular the designs of glyphs or characters in the Fonts may be 72 | modified and additional glyphs or characters may be added to the 73 | Fonts, only if the fonts are renamed to names not containing either 74 | the words "Tavmjong Bah" or the word "Arev". 75 | 76 | This License becomes null and void to the extent applicable to Fonts 77 | or Font Software that has been modified and is distributed under the 78 | "Tavmjong Bah Arev" names. 79 | 80 | The Font Software may be sold as part of a larger software package but 81 | no copy of one or more of the Font Software typefaces may be sold by 82 | itself. 83 | 84 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 85 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 86 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 87 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL 88 | TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 89 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 90 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 91 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 92 | OTHER DEALINGS IN THE FONT SOFTWARE. 93 | 94 | Except as contained in this notice, the name of Tavmjong Bah shall not 95 | be used in advertising or otherwise to promote the sale, use or other 96 | dealings in this Font Software without prior written authorization 97 | from Tavmjong Bah. For further information, contact: tavmjong @ free 98 | . fr. 99 | 100 | TeX Gyre DJV Math 101 | ----------------- 102 | Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. 103 | 104 | Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski 105 | (on behalf of TeX users groups) are in public domain. 106 | 107 | Letters imported from Euler Fraktur from AMSfonts are (c) American 108 | Mathematical Society (see below). 109 | Bitstream Vera Fonts Copyright 110 | Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera 111 | is a trademark of Bitstream, Inc. 112 | 113 | Permission is hereby granted, free of charge, to any person obtaining a copy 114 | of the fonts accompanying this license (“Fonts”) and associated 115 | documentation 116 | files (the “Font Software”), to reproduce and distribute the Font Software, 117 | including without limitation the rights to use, copy, merge, publish, 118 | distribute, 119 | and/or sell copies of the Font Software, and to permit persons to whom 120 | the Font Software is furnished to do so, subject to the following 121 | conditions: 122 | 123 | The above copyright and trademark notices and this permission notice 124 | shall be 125 | included in all copies of one or more of the Font Software typefaces. 126 | 127 | The Font Software may be modified, altered, or added to, and in particular 128 | the designs of glyphs or characters in the Fonts may be modified and 129 | additional 130 | glyphs or characters may be added to the Fonts, only if the fonts are 131 | renamed 132 | to names not containing either the words “Bitstream” or the word “Vera”. 133 | 134 | This License becomes null and void to the extent applicable to Fonts or 135 | Font Software 136 | that has been modified and is distributed under the “Bitstream Vera” 137 | names. 138 | 139 | The Font Software may be sold as part of a larger software package but 140 | no copy 141 | of one or more of the Font Software typefaces may be sold by itself. 142 | 143 | THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 144 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, 145 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, 146 | TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME 147 | FOUNDATION 148 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, 149 | SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN 150 | ACTION 151 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR 152 | INABILITY TO USE 153 | THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. 154 | Except as contained in this notice, the names of GNOME, the GNOME 155 | Foundation, 156 | and Bitstream Inc., shall not be used in advertising or otherwise to promote 157 | the sale, use or other dealings in this Font Software without prior written 158 | authorization from the GNOME Foundation or Bitstream Inc., respectively. 159 | For further information, contact: fonts at gnome dot org. 160 | 161 | AMSFonts (v. 2.2) copyright 162 | 163 | The PostScript Type 1 implementation of the AMSFonts produced by and 164 | previously distributed by Blue Sky Research and Y&Y, Inc. are now freely 165 | available for general use. This has been accomplished through the 166 | cooperation 167 | of a consortium of scientific publishers with Blue Sky Research and Y&Y. 168 | Members of this consortium include: 169 | 170 | Elsevier Science IBM Corporation Society for Industrial and Applied 171 | Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS) 172 | 173 | In order to assure the authenticity of these fonts, copyright will be 174 | held by 175 | the American Mathematical Society. This is not meant to restrict in any way 176 | the legitimate use of the fonts, such as (but not limited to) electronic 177 | distribution of documents containing these fonts, inclusion of these fonts 178 | into other public domain or commercial font collections or computer 179 | applications, use of the outline data to create derivative fonts and/or 180 | faces, etc. However, the AMS does require that the AMS copyright notice be 181 | removed from any derivative versions of the fonts which have been altered in 182 | any way. In addition, to ensure the fidelity of TeX documents using Computer 183 | Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces, 184 | has requested that any alterations which yield different font metrics be 185 | given a different name. 186 | 187 | $Id$ 188 | -------------------------------------------------------------------------------- /episode/4/code/resources/text/wikipedia_en_keyboard.txt: -------------------------------------------------------------------------------- 1 | In computing, a computer keyboard is a typewriter-style device which uses an arrangement of buttons or keys to act as a mechanical lever or electronic switch. Following the decline of punch cards and paper tape, interaction via teleprinter-style keyboards became the main input device for computers. 2 | 3 | Partial text from https://en.wikipedia.org/w/index.php?title=Computer_keyboard&oldid=838809645 4 | 5 | Creative Commons Attribution-ShareAlike 3.0 Unported License 6 | -------------------------------------------------------------------------------- /episode/4/code/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Basic hello world example. 2 | 3 | extern crate ggez; 4 | 5 | use ggez::conf; 6 | use ggez::event; 7 | use ggez::graphics; 8 | use ggez::{Context, GameResult}; 9 | use std::env; 10 | use std::fs::File; 11 | use std::io::Read; 12 | use std::path; 13 | 14 | // First we make a structure to contain the game's state 15 | struct MainState { 16 | text: String, 17 | current: usize, 18 | frames: usize, 19 | font: graphics::Font, 20 | } 21 | 22 | impl MainState { 23 | fn new(ctx: &mut Context) -> GameResult { 24 | // The ttf file will be in your resources directory. Later, we 25 | // will mount that directory so we can omit it in the path here. 26 | let font = graphics::Font::new(ctx, "/font/DejaVuSerif.ttf", 20)?; 27 | 28 | let mut text = String::new(); 29 | let mut f = File::open("resources/text/wikipedia_en_keyboard.txt")?; 30 | f.read_to_string(&mut text); 31 | 32 | let s = MainState { 33 | text, 34 | current: 0, 35 | frames: 0, 36 | font: font, 37 | }; 38 | Ok(s) 39 | } 40 | } 41 | 42 | // Then we implement the `ggez:event::EventHandler` trait on it, which 43 | // requires callbacks for updating and drawing the game state each frame. 44 | // 45 | // The `EventHandler` trait also contains callbacks for event handling 46 | // that you can override if you wish, but the defaults are fine. 47 | impl event::EventHandler for MainState { 48 | fn update(&mut self, _ctx: &mut Context) -> GameResult<()> { 49 | Ok(()) 50 | } 51 | 52 | fn draw(&mut self, ctx: &mut Context) -> GameResult<()> { 53 | graphics::clear(ctx); 54 | 55 | graphics::set_color(ctx, (255, 255, 255).into()); 56 | let (_, lines) = self.font.get_wrap(&self.text, 500); 57 | let mut y = 10.0; 58 | for line in &lines { 59 | let dest_point = graphics::Point2::new(10.0, y); 60 | let text = graphics::Text::new(ctx, line, &self.font)?; 61 | graphics::draw(ctx, &text, dest_point, 0.0)?; 62 | y += 30.0; 63 | } 64 | graphics::set_color(ctx, (255, 0, 0).into()); 65 | let correct_text: String = self.text.chars().take(self.current).collect(); 66 | let (_, lines) = self.font.get_wrap(&correct_text, 500); 67 | let mut y = 10.0; 68 | for line in &lines { 69 | let dest_point = graphics::Point2::new(10.0, y); 70 | let text = graphics::Text::new(ctx, line, &self.font)?; 71 | graphics::draw(ctx, &text, dest_point, 0.0)?; 72 | y += 30.0; 73 | } 74 | graphics::present(ctx); 75 | 76 | Ok(()) 77 | } 78 | 79 | fn text_input_event(&mut self, _ctx: &mut Context, text: String) { 80 | match self.text.chars().nth(self.current) { 81 | Some(next_char) => { 82 | if next_char.to_string() == text 83 | || (next_char.is_whitespace() && text.chars().all(|c| c.is_whitespace())) 84 | { 85 | println!("We have a match {}", next_char); 86 | self.current += 1; 87 | } 88 | } 89 | _ => println!("exit!"), 90 | } 91 | } 92 | } 93 | 94 | pub fn main() { 95 | let c = conf::Conf::new(); 96 | let ctx = &mut Context::load_from_conf("helloworld", "ggez", c).unwrap(); 97 | 98 | // We add the CARGO_MANIFEST_DIR/resources to the filesystem's path 99 | // so that ggez will look in our cargo project directory for files. 100 | if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") { 101 | let mut path = path::PathBuf::from(manifest_dir); 102 | path.push("resources"); 103 | ctx.filesystem.mount(&path, true); 104 | } 105 | 106 | let state = &mut MainState::new(ctx).unwrap(); 107 | 108 | if let Err(e) = event::run(ctx, state) { 109 | println!("Error encountered: {}", e); 110 | } else { 111 | println!("Game exited cleanly."); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /episode/4/code/src/text: -------------------------------------------------------------------------------- 1 | example text 2 | -------------------------------------------------------------------------------- /episode/4/episode.yml: -------------------------------------------------------------------------------- 1 | number: 4 2 | title: Touch Typing Tutor 3 | id: S0Vubd-C5-o 4 | intro: > 5 | I hack on a small application that will help me get better at touch typing. 6 | details: > 7 | Not a tutorial, but just a fun hacking session to build a touch typing application using ggez - a game framework written in Rust. 8 | keywords: 9 | - touch typing 10 | - application 11 | - ggez 12 | - event handling 13 | - game state 14 | - iterators 15 | - hacking 16 | - live-coding 17 | notes: 18 | - "[ggez on Github](https://github.com/ggez/ggez)" 19 | - "[ggez homepage](http://ggez.rs/)" 20 | - "[Type racer](http://typeracer.com/)" 21 | - "[keybr](http://keybr.com/)" 22 | metas: 23 | - "My Keyboard: [Durgod Taurus K320](https://www.aliexpress.com/item/durgod-87-taurus-k320-mechanical-keyboard-using-cherry-mx-switches-pbt-doubleshot-keycaps-brown-blue-black/32845509908.html) (Cherry MX Brown keys + O-Rings)" 24 | licenses: 25 | - "I've used sound effects from [free sound pack by Setuniman](https://freesound.org/people/Setuniman/packs/8199/)" 26 | - "[Curious by Setuniman](https://freesound.org/people/Setuniman/sounds/154907/) available at https://freesound.org/s/154907/" 27 | - "Plop sound by [HerbertBoland](https://freesound.org/people/HerbertBoland/sounds/33369/)" 28 | chapters: 29 | - "Printing text on the screen: 2:12" 30 | - "Event handling in ggez: 6:16" 31 | - "Wrapping text: 11:52" 32 | - "Adding text input: 18:57" 33 | - "Matching input characters: 23:08" 34 | - "Colors!: 28:43" 35 | - "Summary: 37:23" 36 | -------------------------------------------------------------------------------- /episode/4/thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-rust/show/02d40f1db583c3b6da38c18e313e08bfff6b08e7/episode/4/thumb.jpg -------------------------------------------------------------------------------- /episode/5/README.md: -------------------------------------------------------------------------------- 1 | # Episode 5 - Coding Challenge - Balanced Brackets 2 | 3 | [![YouTube video thumbnail](./thumb.jpg)](https://corrode.dev/hello-rust/) 4 | **[▶ Watch now on Youtube!](https://youtu.be/XcuLHO8z_RA)** 5 | 6 | Today we will do a quick interview question called "balanced brackets" 7 | 8 | I maintain a list of common interview questions on Github, that is perfect for some quick fun exercise from time to time. In this case I'd like to introduce you to a task called "balanced brackets", which is some sort of state machine, to check if an input consisting of brackets has exactly one closing bracket for each opening bracket. (Psst... I'm hiding a bug in my code, can you find it? 😉 Answer here: https://github.com/hello-rust/show/pull/31) 9 | 10 | 11 | 12 | If you like to get notified about new episodes, [please subscribe to my channel](https://www.youtube.com/hellorust) 😊. 13 | 14 | Keywords: Into trait, Pattern matching, HashMap, Stack, unreachable! macro, coding puzzle, competitive programming 15 | 16 | ## Things I mentioned during the show 17 | 18 | * Unreachable macro: https://doc.rust-lang.org/std/macro.unreachable.html 19 | * Into trait: https://doc.rust-lang.org/std/convert/trait.Into.html 20 | * Creating a Rust function that accepts String or &str: https://hermanradtke.com/2015/05/06/creating-a-rust-function-that-accepts-string-or-str.html 21 | 22 | ## Things I should have mentioned (but forgot) 23 | 24 | * Thanks to @barskern who [found a major bug in the code](https://github.com/hello-rust/show/blob/master/episode/5/balanced/src/lib.rs) 25 | * More coding puzzles at [my Github repository](https://github.com/mre/the-coding-interview) 26 | 27 | 28 | ## Resources and licenses 29 | 30 | * Bavarian Seaside by [KevinMacLeod](https://soundcloud.com/kevin-9-1/bavarian-seascape) 31 | * Coin sound by [ProjectsU012](https://freesound.org/people/ProjectsU012/sounds/341695/) 32 | * Blip sound by [ProjectsU012](https://freesound.org/people/ProjectsU012/sounds/341024/) 33 | * Mystery sound by [FoolBoyMedia](https://freesound.org/people/FoolBoyMedia/sounds/256099/#) 34 | 35 | 36 | 37 | ## Support! 38 | 39 | Preparing, recording, and editing an episode takes a substantial amount of time 40 | (around 30 hours total). I do all of this next to my fulltime dayjob. 41 | If you want to show your appreciation and help me keep the content free 42 | for everybody to enjoy, [please consider sponsoring me on 43 | Github](https://github.com/sponsors/mre/) - no matter the amount. ❤️ -------------------------------------------------------------------------------- /episode/5/balanced/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "balanced" 3 | version = "0.1.0" 4 | 5 | -------------------------------------------------------------------------------- /episode/5/balanced/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "balanced" 3 | version = "0.1.0" 4 | authors = ["Matthias Endler "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /episode/5/balanced/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | pub fn balanced>(input: T) -> bool { 4 | let mut stack = Vec::new(); 5 | 6 | let mut matches = HashMap::new(); 7 | matches.insert(')', '('); 8 | matches.insert(']', '['); 9 | matches.insert('}', '{'); 10 | 11 | for c in input.into().chars() { 12 | match c { 13 | '(' | '[' | '{' => stack.push(c), 14 | ')' | ']' | '}' => { 15 | let prev = stack.pop(); 16 | match matches.get(&c) { 17 | Some(prev) => (), 18 | _ => unreachable!(), 19 | } 20 | } 21 | _ => return false, 22 | } 23 | } 24 | stack.len() == 0 25 | } 26 | 27 | #[cfg(test)] 28 | mod tests { 29 | use super::balanced; 30 | 31 | #[test] 32 | fn it_works() { 33 | assert_eq!(balanced(""), true); 34 | assert_eq!(balanced("()"), true); 35 | assert_eq!(balanced("("), false); 36 | assert_eq!(balanced("()[]{}"), true); 37 | assert_eq!(balanced("{()[]{}[]}"), true); 38 | assert_eq!(balanced("{(())[[{}]]{}[]}"), true); 39 | assert_eq!(balanced("{(())[[{}]]{}[]}hello"), false); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /episode/5/episode.yml: -------------------------------------------------------------------------------- 1 | number: 5 2 | title: Coding Challenge - Balanced Brackets 3 | id: XcuLHO8z_RA 4 | intro: > 5 | Today we will do a quick interview question called "balanced brackets" 6 | details: > 7 | I maintain a list of common interview questions on Github, that is perfect for some quick fun exercise from time to time. 8 | In this case I'd like to introduce you to a task called "balanced brackets", which is some sort of state machine, 9 | to check if an input consisting of brackets has exactly one closing bracket for each opening bracket. 10 | (Psst... I'm hiding a bug in my code, can you find it? 😉 Answer here: https://github.com/hello-rust/show/pull/31) 11 | keywords: 12 | - Into trait 13 | - Pattern matching 14 | - HashMap 15 | - Stack 16 | - unreachable! macro 17 | - coding puzzle 18 | - competitive programming 19 | notes: 20 | - "Unreachable macro: https://doc.rust-lang.org/std/macro.unreachable.html" 21 | - "Into trait: https://doc.rust-lang.org/std/convert/trait.Into.html" 22 | - "Creating a Rust function that accepts String or &str: https://hermanradtke.com/2015/05/06/creating-a-rust-function-that-accepts-string-or-str.html" 23 | others: 24 | - "Thanks to @barskern who [found a major bug in the code](https://github.com/hello-rust/show/blob/master/episode/5/balanced/src/lib.rs)" 25 | - "More coding puzzles at [my Github repository](https://github.com/mre/the-coding-interview)" 26 | licenses: 27 | - "Bavarian Seaside by [KevinMacLeod](https://soundcloud.com/kevin-9-1/bavarian-seascape)" 28 | - "Coin sound by [ProjectsU012](https://freesound.org/people/ProjectsU012/sounds/341695/)" 29 | - "Blip sound by [ProjectsU012](https://freesound.org/people/ProjectsU012/sounds/341024/)" 30 | - "Mystery sound by [FoolBoyMedia](https://freesound.org/people/FoolBoyMedia/sounds/256099/#)" 31 | chapters: 32 | - "Straight to the code!: 1:28" 33 | - "Into trait: 2:12" 34 | - "Using a stack for keeping state: 3:13" 35 | - "A hashmap for matching our input: 5:33" 36 | - "unreachable! macro: 7:14" 37 | - "A mysterious bug: 8:07" 38 | - "Adding more tests: 9:05" 39 | - "Wrapping things up: 9:50" 40 | -------------------------------------------------------------------------------- /episode/5/thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-rust/show/02d40f1db583c3b6da38c18e313e08bfff6b08e7/episode/5/thumb.jpg -------------------------------------------------------------------------------- /episode/6/README.md: -------------------------------------------------------------------------------- 1 | # Episode 6 - Parameterized Tests, Macros, And Refactoring 2 | 3 | [![YouTube video thumbnail](./thumb.jpg)](https://corrode.dev/hello-rust/) 4 | **[▶ Watch now on Youtube!](https://youtu.be/XJPci7GI-qg)** 5 | 6 | Let's fix a bug in my balanced brackets code and learn a few things about test organization and traits 7 | There's some stuff to improve in my coding puzzle code from last time, balanced brackets. Can you spot the bug and fix it? I'll show you what's wrong and an idiomatic way to solve the problem. 8 | 9 | 10 | 11 | If you like to get notified about new episodes, [please subscribe to my channel](https://www.youtube.com/hellorust) 😊. 12 | 13 | Keywords: Test data providers, Parameterized tests, AsRef trait, Macros 14 | 15 | ## Things I mentioned during the show 16 | 17 | * [AsRef trait](https://doc.rust-lang.org/std/convert/trait.AsRef.html) 18 | * [Discussion around Into, AsRef and Cow](https://users.rust-lang.org/t/idiomatic-string-parmeter-types-str-vs-asref-str-vs-into-string/7934/4) 19 | * [Macros in Rust](https://doc.rust-lang.org/book/second-edition/appendix-04-macros.html) 20 | 21 | ## Resources and licenses 22 | 23 | * Comic suspense sound by tyops: https://freesound.org/people/tyops/sounds/347221/ 24 | * Background music: [Juanitos - Hola_Hola_Bossa_Nova](http://freemusicarchive.org/music/Juanitos/) 25 | 26 | 27 | 28 | ## Support! 29 | 30 | Preparing, recording, and editing an episode takes a substantial amount of time 31 | (around 30 hours total). I do all of this next to my fulltime dayjob. 32 | If you want to show your appreciation and help me keep the content free 33 | for everybody to enjoy, [please consider sponsoring me on 34 | Github](https://github.com/sponsors/mre/) - no matter the amount. ❤️ -------------------------------------------------------------------------------- /episode/6/balanced/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "balanced" 3 | version = "0.1.0" 4 | 5 | -------------------------------------------------------------------------------- /episode/6/balanced/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "balanced" 3 | version = "0.1.0" 4 | authors = ["Matthias Endler "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /episode/6/balanced/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | pub fn balanced(input: impl AsRef) -> bool { 4 | let mut stack = Vec::new(); 5 | 6 | let mut matches = HashMap::new(); 7 | matches.insert(')', '('); 8 | matches.insert(']', '['); 9 | matches.insert('}', '{'); 10 | 11 | for c in input.as_ref().chars() { 12 | match c { 13 | '(' | '[' | '{' => stack.push(c), 14 | ')' | ']' | '}' => if matches.get(&c) != stack.pop().as_ref() { 15 | return false; 16 | }, 17 | _ => return false, 18 | } 19 | } 20 | stack.len() == 0 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use super::balanced; 26 | 27 | macro_rules! parameterized { 28 | ($($name:ident: $value:expr,)*) => { 29 | $( 30 | #[test] 31 | fn $name() { 32 | let (input, expected) = $value; 33 | assert_eq!(expected, balanced(input)); 34 | } 35 | )* 36 | } 37 | } 38 | 39 | parameterized! { 40 | empty: ("", true), 41 | simple_parenthesis: ("()", true), 42 | multiple_brackets: ("()[]{}", true), 43 | nested_brackets: ("{()[]{}[]}", true), 44 | } 45 | 46 | parameterized! { 47 | missing_close: ("(", false), 48 | missing_open: (")", false), 49 | non_bracket_input: ("{(())[[{}]]{}[]}hello", false), 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /episode/6/episode.yml: -------------------------------------------------------------------------------- 1 | number: 6 2 | title: Parameterized tests, macros, and refactoring 3 | id: XJPci7GI-qg 4 | intro: Let's fix a bug in my balanced brackets code and learn a few things about test organization and traits 5 | details: > 6 | There's some stuff to improve in my coding puzzle code from last time, balanced brackets. 7 | Can you spot the bug and fix it? I'll show you what's wrong and an idiomatic way to solve the problem. 8 | improvments: 9 | - "[la10736](https://github.com/la10736) added [use rstest instead macros](https://github.com/hello-rust/show/pull/50) that use rstest_parametrize to create parametrized tests." 10 | keywords: 11 | - Test data providers 12 | - Parameterized tests 13 | - AsRef trait 14 | - Macros 15 | notes: 16 | - "[AsRef trait](https://doc.rust-lang.org/std/convert/trait.AsRef.html)" 17 | - "[Discussion around Into, AsRef and Cow](https://users.rust-lang.org/t/idiomatic-string-parmeter-types-str-vs-asref-str-vs-into-string/7934/4)" 18 | - "[Macros in Rust](https://doc.rust-lang.org/book/second-edition/appendix-04-macros.html)" 19 | licenses: 20 | - "Comic suspense sound by tyops: https://freesound.org/people/tyops/sounds/347221/" 21 | - "Background music: [Juanitos - Hola_Hola_Bossa_Nova](http://freemusicarchive.org/music/Juanitos/)" 22 | chapters: 23 | - "Straight to the code: 0:51" 24 | - "Test reorganization: 3:46" 25 | - "Parameterized tests using macros: 7:00" 26 | - "Replacing Into with AsRef: 12:33" 27 | - "Impl Trait: 14:35" 28 | -------------------------------------------------------------------------------- /episode/6/parametrize/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "balanced" 3 | version = "0.1.0" 4 | 5 | [[package]] 6 | name = "parametrize" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "balanced 0.1.0", 10 | "rstest 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 11 | ] 12 | 13 | [[package]] 14 | name = "proc-macro2" 15 | version = "0.4.24" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | dependencies = [ 18 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 19 | ] 20 | 21 | [[package]] 22 | name = "quote" 23 | version = "0.6.10" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | dependencies = [ 26 | "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", 27 | ] 28 | 29 | [[package]] 30 | name = "rstest" 31 | version = "0.2.2" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | dependencies = [ 34 | "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", 35 | "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", 36 | "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", 37 | ] 38 | 39 | [[package]] 40 | name = "syn" 41 | version = "0.14.9" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | dependencies = [ 44 | "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", 45 | "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 47 | ] 48 | 49 | [[package]] 50 | name = "unicode-xid" 51 | version = "0.1.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | 54 | [metadata] 55 | "checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09" 56 | "checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c" 57 | "checksum rstest 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "17060b44b74f0aed4e7ee6c970e57b5e51adbd3aecd814e1ab38a27e00901d67" 58 | "checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" 59 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 60 | -------------------------------------------------------------------------------- /episode/6/parametrize/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parametrize" 3 | version = "0.1.0" 4 | authors = ["Michele d'Amico "] 5 | 6 | [dependencies] 7 | "balanced" = { path = "../balanced" } 8 | 9 | [dev-dependencies] 10 | "rstest" = "0.2" -------------------------------------------------------------------------------- /episode/6/parametrize/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate balanced; 2 | #[cfg(test)] 3 | extern crate rstest; 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use balanced::balanced; 8 | use rstest::rstest_parametrize; 9 | 10 | #[rstest_parametrize(value, 11 | case(""), 12 | case("()"), 13 | case("()[]{}"), 14 | case("{()[]{}[]}") 15 | )] 16 | fn is_balanced(value: &str) { 17 | assert!(balanced(value)) 18 | } 19 | 20 | #[rstest_parametrize(value, 21 | case("("), 22 | case(")"), 23 | case("{(())[[{}]]{}[]}hello") 24 | )] 25 | fn is_not_balanced(value: &str) { 26 | assert!(!balanced(value)) 27 | } 28 | 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /episode/6/thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-rust/show/02d40f1db583c3b6da38c18e313e08bfff6b08e7/episode/6/thumb.jpg -------------------------------------------------------------------------------- /episode/7/README.md: -------------------------------------------------------------------------------- 1 | # Episode 7 - Parsing Dates Using Proptest And Tdd 2 | 3 | [![YouTube video thumbnail](./thumb.jpg)](https://corrode.dev/hello-rust/) 4 | **[▶ Watch now on Youtube!](https://youtu.be/zb7SD0Jco6g)** 5 | 6 | Testing is hard, as can be seen in the last two episodes. That's why I looked around for better alternatives and found proptest. 7 | Let's take a look at proptest, a library for automatically generating testcases similar to quickcheck (of Haskell fame). We will parse informal english dates like "4th of September 1927" using Proptest and TDD. 8 | 9 | 10 | 11 | If you like to get notified about new episodes, [please subscribe to my channel](https://www.youtube.com/hellorust) 😊. 12 | 13 | Keywords: Property testing, Quickcheck, Unit testing, Fuzzy testing, TDD 14 | 15 | ## Things I mentioned during the show 16 | 17 | * [Proptest](https://github.com/AltSysrq/proptest) crate by Jason Lingle 18 | * [Quickcheck crate for Rust](https://github.com/BurntSushi/quickcheck) by Andrew Gallant 19 | * [History of quickcheck](https://en.wikipedia.org/wiki/QuickCheck) 20 | * [Chrono english crate](https://github.com/stevedonovan/chrono-english) by Steve J Donovan 21 | 22 | ## Errata and improvements 23 | 24 | It might come as a surprise to you, but every once in a while *even I* make a mistake. 25 | This section covers all improvements made to the code since the epsiode went live. 26 | For an exhaustive list of all changes to the original code, [go here](https://github.com/hello-rust/show/commits/master/episode/7). 27 | Thanks to all contributors! 28 | 29 | * [vikrrrr](https://github.com/vikrrrr) found [a beautiful way to parse the month using Option::map](https://github.com/hello-rust/show/pull/42). 30 | 31 | ## Meta 32 | 33 | * I prefer to use `ok_or_else` as `ok_or` evaluates eagerly and [might get deprecated](https://github.com/rust-lang/rust/issues/51292) 34 | 35 | 36 | ## Resources and licenses 37 | 38 | * Pizzicato by Setuniman: https://freesound.org/people/Setuniman/sounds/149827/ 39 | * Lynx pizzicanto by filmistro: https://filmstro.com/music/pizzicato-strings 40 | * Bug image: https://www.freepik.com/free-vector/funny-insects-icon-set_1488662.htm#term=bug&page=1&position=9 41 | 42 | 43 | 44 | ## Support! 45 | 46 | Preparing, recording, and editing an episode takes a substantial amount of time 47 | (around 30 hours total). I do all of this next to my fulltime dayjob. 48 | If you want to show your appreciation and help me keep the content free 49 | for everybody to enjoy, [please consider sponsoring me on 50 | Github](https://github.com/sponsors/mre/) - no matter the amount. ❤️ -------------------------------------------------------------------------------- /episode/7/episode.yml: -------------------------------------------------------------------------------- 1 | number: 7 2 | title: Parsing dates using proptest and TDD 3 | id: zb7SD0Jco6g 4 | intro: Testing is hard, as can be seen in the last two episodes. That's why I looked around for better alternatives and found proptest. 5 | details: > 6 | Let's take a look at proptest, a library for automatically generating testcases similar to quickcheck (of Haskell fame). 7 | We will parse informal english dates like "4th of September 1927" using Proptest and TDD. 8 | keywords: 9 | - Property testing 10 | - Quickcheck 11 | - Unit testing 12 | - Fuzzy testing 13 | - TDD 14 | notes: 15 | - "[Proptest](https://github.com/AltSysrq/proptest) crate by Jason Lingle" 16 | - "[Quickcheck crate for Rust](https://github.com/BurntSushi/quickcheck) by Andrew Gallant" 17 | - "[History of quickcheck](https://en.wikipedia.org/wiki/QuickCheck)" 18 | - "[Chrono english crate](https://github.com/stevedonovan/chrono-english) by Steve J Donovan" 19 | errata: 20 | - "[vikrrrr](https://github.com/vikrrrr) found [a beautiful way to parse the month using Option::map](https://github.com/hello-rust/show/pull/42)." 21 | metas: 22 | - "I prefer to use `ok_or_else` as `ok_or` evaluates eagerly and [might get deprecated](https://github.com/rust-lang/rust/issues/51292)" 23 | licenses: 24 | - "Pizzicato by Setuniman: https://freesound.org/people/Setuniman/sounds/149827/" 25 | - "Lynx pizzicanto by filmistro: https://filmstro.com/music/pizzicato-strings" 26 | - "Bug image: https://www.freepik.com/free-vector/funny-insects-icon-set_1488662.htm#term=bug&page=1&position=9" 27 | chapters: 28 | - "Straight to the code!: 0:46" 29 | - "Our first test: 8:03" 30 | - "Bug fixing countdown: 14:09" 31 | - "Vector lookup using position(): 21:15" 32 | - "Introduction to proptest: 33:38" 33 | - "Hello Campino: 47:41" 34 | - "Summary: 55:42" 35 | -------------------------------------------------------------------------------- /episode/7/humandate/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "bit-set" 3 | version = "0.5.0" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "bit-vec 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "bit-vec" 11 | version = "0.5.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | 14 | [[package]] 15 | name = "bitflags" 16 | version = "1.0.3" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | 19 | [[package]] 20 | name = "chrono" 21 | version = "0.4.2" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | dependencies = [ 24 | "num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", 25 | "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 26 | "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 27 | ] 28 | 29 | [[package]] 30 | name = "fuchsia-zircon" 31 | version = "0.3.3" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | dependencies = [ 34 | "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 35 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 36 | ] 37 | 38 | [[package]] 39 | name = "fuchsia-zircon-sys" 40 | version = "0.3.3" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | 43 | [[package]] 44 | name = "humandate" 45 | version = "0.1.0" 46 | dependencies = [ 47 | "chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 48 | "proptest 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 49 | ] 50 | 51 | [[package]] 52 | name = "lazy_static" 53 | version = "1.0.1" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | 56 | [[package]] 57 | name = "libc" 58 | version = "0.2.41" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | 61 | [[package]] 62 | name = "num-integer" 63 | version = "0.1.38" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | dependencies = [ 66 | "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 67 | ] 68 | 69 | [[package]] 70 | name = "num-traits" 71 | version = "0.2.4" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | 74 | [[package]] 75 | name = "proptest" 76 | version = "0.7.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | dependencies = [ 79 | "bit-set 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 80 | "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 81 | "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 82 | "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 83 | "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 85 | "regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 86 | ] 87 | 88 | [[package]] 89 | name = "quick-error" 90 | version = "1.2.2" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | 93 | [[package]] 94 | name = "rand" 95 | version = "0.4.2" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | dependencies = [ 98 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 99 | "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", 100 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 101 | ] 102 | 103 | [[package]] 104 | name = "redox_syscall" 105 | version = "0.1.38" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | 108 | [[package]] 109 | name = "regex-syntax" 110 | version = "0.4.2" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | 113 | [[package]] 114 | name = "time" 115 | version = "0.1.40" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | dependencies = [ 118 | "libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)", 119 | "redox_syscall 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", 120 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 121 | ] 122 | 123 | [[package]] 124 | name = "winapi" 125 | version = "0.3.4" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | dependencies = [ 128 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 129 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 130 | ] 131 | 132 | [[package]] 133 | name = "winapi-i686-pc-windows-gnu" 134 | version = "0.4.0" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | 137 | [[package]] 138 | name = "winapi-x86_64-pc-windows-gnu" 139 | version = "0.4.0" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | 142 | [metadata] 143 | "checksum bit-set 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6f1efcc46c18245a69c38fcc5cc650f16d3a59d034f3106e9ed63748f695730a" 144 | "checksum bit-vec 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4440d5cb623bb7390ae27fec0bb6c61111969860f8e3ae198bfa0663645e67cf" 145 | "checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789" 146 | "checksum chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1cce36c92cb605414e9b824f866f5babe0a0368e39ea07393b9b63cf3844c0e6" 147 | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 148 | "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 149 | "checksum lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739" 150 | "checksum libc 0.2.41 (registry+https://github.com/rust-lang/crates.io-index)" = "ac8ebf8343a981e2fa97042b14768f02ed3e1d602eac06cae6166df3c8ced206" 151 | "checksum num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac0ea58d64a89d9d6b7688031b3be9358d6c919badcf7fbb0527ccfd891ee45" 152 | "checksum num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "775393e285254d2f5004596d69bb8bc1149754570dcc08cf30cabeba67955e28" 153 | "checksum proptest 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a3ff101e7a7be1104b3d71f194bc10a3fa338e89b3539444cfde6fdb3aae94a1" 154 | "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" 155 | "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" 156 | "checksum redox_syscall 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "0a12d51a5b5fd700e6c757f15877685bfa04fd7eb60c108f01d045cafa0073c2" 157 | "checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e" 158 | "checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" 159 | "checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" 160 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 161 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 162 | -------------------------------------------------------------------------------- /episode/7/humandate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "humandate" 3 | version = "0.1.0" 4 | authors = ["Matthias Endler "] 5 | 6 | [dependencies] 7 | chrono = "0.4.2" 8 | 9 | [dev-dependencies] 10 | proptest = "0.7.0" -------------------------------------------------------------------------------- /episode/7/humandate/proptest-regressions/lib.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | xs 1238787609 3701346337 1866156833 2552989754 # shrinks to ref s = "0000-00-00" 8 | xs 3051926889 1888815014 1768961892 1774011593 # shrinks to ref s = "32st of January 0000" 9 | xs 1567262811 3345674159 1529742834 2184172309 # shrinks to ref s = "32nd of January 0000" 10 | xs 371479063 2971829295 1122913582 4135108498 # shrinks to ref s = "00th of January 0000" 11 | xs 3009340268 1481089089 960167167 3150609670 # shrinks to ref s = "29th of February 0001" 12 | xs 850268817 262905739 3306983655 288823461 # shrinks to ref s = "01st of January 262144" 13 | xs 574876970 2382342463 1169404200 4175189671 # shrinks to y = 0, m = 9, d = 31 14 | xs 2826456505 1944994900 3866608525 3712069189 # shrinks to ref s = "a a a 0" 15 | xs 1453637732 1480878277 2002291066 1751998472 # shrinks to ref s = "0000 a a 0" 16 | -------------------------------------------------------------------------------- /episode/7/humandate/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate chrono; 2 | 3 | #[cfg(test)] 4 | #[macro_use] 5 | extern crate proptest; 6 | 7 | use chrono::NaiveDate; 8 | 9 | static MONTH_NAMES: [&str; 12] = [ 10 | "January", 11 | "February", 12 | "March", 13 | "April", 14 | "May", 15 | "June", 16 | "July", 17 | "August", 18 | "September", 19 | "October", 20 | "November", 21 | "December", 22 | ]; 23 | 24 | #[derive(Debug, PartialEq)] 25 | pub enum Error { 26 | ParseError, 27 | InvalidDateError, 28 | InvalidDayError, 29 | } 30 | 31 | impl From for Error { 32 | fn from(T: std::num::ParseIntError) -> Error { 33 | Error::ParseError 34 | } 35 | } 36 | 37 | fn parse_month(month: &str) -> Option { 38 | MONTH_NAMES.iter().position(|&elem| elem == month).map(|day| day + 1) 39 | } 40 | 41 | fn parse_day(day_with_ordinal: &str) -> Result { 42 | let day: u32 = day_with_ordinal 43 | .chars() 44 | .take_while(|c| c.is_digit(10)) 45 | .collect::() 46 | .parse::()?; 47 | let ordinal = day_with_ordinal 48 | .chars() 49 | .skip_while(|c| c.is_digit(10)) 50 | .collect::(); 51 | 52 | match (day, ordinal.as_ref()) { 53 | (1, "st") 54 | | (2, "nd") 55 | | (3, "rd") 56 | | (4...20, "th") 57 | | (21, "st") 58 | | (22, "nd") 59 | | (23, "rd") 60 | | (24...30, "th") 61 | | (31, "st") => Ok(day), 62 | _ => Err(Error::InvalidDayError), 63 | } 64 | } 65 | 66 | /// Parses a human-readable date format: 67 | /// 4th of July 2018 68 | /// 2nd of March 2006 69 | /// 3rd of November 1975 70 | pub fn parse_date(humandate: impl AsRef) -> Result { 71 | let parts: Vec<&str> = humandate.as_ref().split_whitespace().collect(); 72 | if parts.len() != 4 { 73 | return Err(Error::ParseError); 74 | } 75 | let day: u32 = parse_day(parts[0])?; 76 | 77 | // TODO: parts[1] == of 78 | 79 | let month: u32 = parse_month(parts[2]).ok_or_else(|| Error::ParseError)? as u32; 80 | let year: i32 = parts[3].parse()?; 81 | NaiveDate::from_ymd_opt(year, month, day).ok_or_else(|| Error::InvalidDateError) 82 | } 83 | 84 | #[cfg(test)] 85 | mod tests { 86 | use super::*; 87 | 88 | #[test] 89 | fn it_works() { 90 | assert_eq!( 91 | parse_date("04th of September 2015"), 92 | Ok(NaiveDate::from_ymd(2015, 9, 4)) 93 | ); 94 | assert_eq!( 95 | parse_date("05th of September 2015"), 96 | Ok(NaiveDate::from_ymd(2015, 9, 5)) 97 | ); 98 | assert_eq!( 99 | parse_date("1st of September 2015"), 100 | Ok(NaiveDate::from_ymd(2015, 9, 1)) 101 | ); 102 | } 103 | 104 | proptest! { 105 | #[test] 106 | fn doesnt_crash(ref s in "\\PC*") { 107 | parse_date(s); 108 | } 109 | 110 | #[test] 111 | fn handles_invalid_words(ref s in "([0-9a-z]{1,5} ){3}[0-9a-z]{1,5}") { 112 | let _ = parse_date(s); 113 | } 114 | 115 | #[test] 116 | fn parses_date_back_to_original(y in 0i32..10000, m in 0usize..12, d in 1u32..32) { 117 | let human_month = MONTH_NAMES[m]; 118 | let ordinal = match d { 119 | 1 | 21 | 31 => "st", 120 | 2 | 22 => "nd", 121 | 3 | 23 => "rd", 122 | _ => "th", 123 | }; 124 | let date_string = format!("{}{} of {} {}", d, ordinal, human_month,y); 125 | println!("{}", date_string); 126 | assert_eq!(parse_date(date_string), chrono::NaiveDate::from_ymd_opt(y, (m + 1) as u32, d).ok_or_else(|| Error::InvalidDateError)); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /episode/7/thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-rust/show/02d40f1db583c3b6da38c18e313e08bfff6b08e7/episode/7/thumb.jpg -------------------------------------------------------------------------------- /episode/8/README.md: -------------------------------------------------------------------------------- 1 | # Episode 8 - Let'S Write A Python Module! 2 | 3 | [![YouTube video thumbnail](./thumb.jpg)](https://corrode.dev/hello-rust/) 4 | **[▶ Watch now on Youtube!](https://youtu.be/D9r__qxtRMQ)** 5 | 6 | Today, I want to show you how to write a Python extension in Rust using pyo3. 7 | I like tinkering with programming languages and making things faster. What's cooler than combining those two things? With Rust you can write safe, fast extensions for dynamically typed programming languages like Python, PHP, or Ruby. That's why today i'd like to show you how to write a Python extension from scratch! 8 | 9 | 10 | 11 | If you like to get notified about new episodes, [please subscribe to my channel](https://www.youtube.com/hellorust) 😊. 12 | 13 | Keywords: Tutorial, FFI, pyo3, Module, Extension, Python 14 | 15 | ## Things I mentioned during the show 16 | 17 | * A nightly compiler is required. You can use [rustup](https://rustup.rs/) to install it. 18 | * To use your compiled module, run the Python interpreter outside of the project folder. 19 | * Here are a few ideas for your own module: Write a regex module based on Rust's re or write a str.replace method. 20 | * If you like this stuff check out [hyperjson](https://github.com/mre/hyperjson), a json module I wrote in Rust. 21 | * setup.py (for humans): https://github.com/kennethreitz/setup.py. 22 | 23 | ## Errata and improvements 24 | 25 | It might come as a surprise to you, but every once in a while *even I* make a mistake. 26 | This section covers all improvements made to the code since the epsiode went live. 27 | For an exhaustive list of all changes to the original code, [go here](https://github.com/hello-rust/show/commits/master/episode/8). 28 | Thanks to all contributors! 29 | 30 | * Update code to work with latest version of pyo3; formatting and cleanup for stabilizations. See [changes here](https://github.com/hello-rust/show/pull/47). 31 | 32 | ## Meta 33 | 34 | * Visual Studio Code Theme: Monokai 35 | 36 | 37 | ## Resources and licenses 38 | 39 | * B-Roll: Computer And The Mind Of Man Pt 3, The Universal Machine: https://archive.org/details/ComputerAndTheMindOfManP3TheUniversalMachine. 40 | * Radio static by LimitSnap_Creations: https://freesound.org/people/LimitSnap_Creations/sounds/279003/. 41 | * silent movie 0M_34pi by Setuniman: https://freesound.org/people/Setuniman/sounds/153470/. 42 | * 'Absurd' by David Fesliyan from http://fesliyanstudios.com/. 43 | * 'Moron' by David Fesliyan from http://fesliyanstudios.com/. 44 | * Baddum Tish - Comedy Rimshots.wav by rodincoil: https://freesound.org/people/rodincoil/sounds/271208/. 45 | 46 | 47 | 48 | ## Support! 49 | 50 | Preparing, recording, and editing an episode takes a substantial amount of time 51 | (around 30 hours total). I do all of this next to my fulltime dayjob. 52 | If you want to show your appreciation and help me keep the content free 53 | for everybody to enjoy, [please consider sponsoring me on 54 | Github](https://github.com/sponsors/mre/) - no matter the amount. ❤️ -------------------------------------------------------------------------------- /episode/8/episode.yml: -------------------------------------------------------------------------------- 1 | number: 8 2 | title: Let's write a Python module! 3 | id: D9r__qxtRMQ 4 | intro: Today, I want to show you how to write a Python extension in Rust using pyo3. 5 | details: > 6 | I like tinkering with programming languages and making things faster. What's cooler than combining those two things? 7 | With Rust you can write safe, fast extensions for dynamically typed programming languages like Python, PHP, or Ruby. 8 | That's why today i'd like to show you how to write a Python extension from scratch! 9 | keywords: 10 | - Tutorial 11 | - FFI 12 | - pyo3 13 | - Module 14 | - Extension 15 | - Python 16 | notes: 17 | - "A nightly compiler is required. You can use [rustup](https://rustup.rs/) to install it." 18 | - "To use your compiled module, run the Python interpreter outside of the project folder." 19 | - "Here are a few ideas for your own module: Write a regex module based on Rust's re or write a str.replace method." 20 | - "If you like this stuff check out [hyperjson](https://github.com/mre/hyperjson), a json module I wrote in Rust." 21 | - "setup.py (for humans): https://github.com/kennethreitz/setup.py." 22 | errata: 23 | - "Update code to work with latest version of pyo3; formatting and cleanup for 24 | stabilizations. See [changes here](https://github.com/hello-rust/show/pull/47)." 25 | metas: 26 | - "Visual Studio Code Theme: Monokai" 27 | licenses: 28 | - "B-Roll: Computer And The Mind Of Man Pt 3, The Universal Machine: https://archive.org/details/ComputerAndTheMindOfManP3TheUniversalMachine." 29 | - "Radio static by LimitSnap_Creations: https://freesound.org/people/LimitSnap_Creations/sounds/279003/." 30 | - "silent movie 0M_34pi by Setuniman: https://freesound.org/people/Setuniman/sounds/153470/." 31 | - "'Absurd' by David Fesliyan from http://fesliyanstudios.com/." 32 | - "'Moron' by David Fesliyan from http://fesliyanstudios.com/." 33 | - "Baddum Tish - Comedy Rimshots.wav by rodincoil: https://freesound.org/people/rodincoil/sounds/271208/." 34 | chapters: 35 | -------------------------------------------------------------------------------- /episode/8/lenrs/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | __pycache__ 3 | *.egg-info 4 | dist -------------------------------------------------------------------------------- /episode/8/lenrs/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aho-corasick" 5 | version = "0.7.3" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "0.1.4" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | 16 | [[package]] 17 | name = "cfg-if" 18 | version = "0.1.9" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | 21 | [[package]] 22 | name = "lazy_static" 23 | version = "1.3.0" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | 26 | [[package]] 27 | name = "lenrs" 28 | version = "0.1.0" 29 | dependencies = [ 30 | "pyo3 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 31 | ] 32 | 33 | [[package]] 34 | name = "libc" 35 | version = "0.2.55" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | 38 | [[package]] 39 | name = "log" 40 | version = "0.4.6" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | dependencies = [ 43 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 44 | ] 45 | 46 | [[package]] 47 | name = "mashup" 48 | version = "0.1.9" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | dependencies = [ 51 | "mashup-impl 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 52 | "proc-macro-hack 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 53 | ] 54 | 55 | [[package]] 56 | name = "mashup-impl" 57 | version = "0.1.9" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | dependencies = [ 60 | "proc-macro-hack 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 61 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 62 | ] 63 | 64 | [[package]] 65 | name = "memchr" 66 | version = "2.2.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | 69 | [[package]] 70 | name = "num-traits" 71 | version = "0.2.8" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | dependencies = [ 74 | "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 75 | ] 76 | 77 | [[package]] 78 | name = "proc-macro-hack" 79 | version = "0.4.1" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | dependencies = [ 82 | "proc-macro-hack-impl 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 83 | ] 84 | 85 | [[package]] 86 | name = "proc-macro-hack-impl" 87 | version = "0.4.1" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | 90 | [[package]] 91 | name = "proc-macro2" 92 | version = "0.4.30" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | dependencies = [ 95 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 96 | ] 97 | 98 | [[package]] 99 | name = "pyo3" 100 | version = "0.4.1" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | dependencies = [ 103 | "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", 104 | "mashup 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 105 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 106 | "pyo3cls 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 107 | "regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 108 | "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", 109 | "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 110 | ] 111 | 112 | [[package]] 113 | name = "pyo3-derive-backend" 114 | version = "0.4.1" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | dependencies = [ 117 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 118 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 119 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 120 | "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", 121 | ] 122 | 123 | [[package]] 124 | name = "pyo3cls" 125 | version = "0.4.1" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | dependencies = [ 128 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 129 | "pyo3-derive-backend 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 130 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 131 | "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", 132 | ] 133 | 134 | [[package]] 135 | name = "quote" 136 | version = "0.6.12" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | dependencies = [ 139 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 140 | ] 141 | 142 | [[package]] 143 | name = "regex" 144 | version = "1.1.6" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | dependencies = [ 147 | "aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", 148 | "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 149 | "regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 150 | "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 151 | "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 152 | ] 153 | 154 | [[package]] 155 | name = "regex-syntax" 156 | version = "0.6.6" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | dependencies = [ 159 | "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 160 | ] 161 | 162 | [[package]] 163 | name = "spin" 164 | version = "0.4.10" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | 167 | [[package]] 168 | name = "syn" 169 | version = "0.14.9" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | dependencies = [ 172 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 173 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 174 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 175 | ] 176 | 177 | [[package]] 178 | name = "thread_local" 179 | version = "0.3.6" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | dependencies = [ 182 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 183 | ] 184 | 185 | [[package]] 186 | name = "ucd-util" 187 | version = "0.1.3" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | 190 | [[package]] 191 | name = "unicode-xid" 192 | version = "0.1.0" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | 195 | [[package]] 196 | name = "utf8-ranges" 197 | version = "1.0.2" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | 200 | [[package]] 201 | name = "version_check" 202 | version = "0.1.5" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | 205 | [metadata] 206 | "checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c" 207 | "checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf" 208 | "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" 209 | "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" 210 | "checksum libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "42914d39aad277d9e176efbdad68acb1d5443ab65afe0e0e4f0d49352a950880" 211 | "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" 212 | "checksum mashup 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f2d82b34c7fb11bb41719465c060589e291d505ca4735ea30016a91f6fc79c3b" 213 | "checksum mashup-impl 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "aa607bfb674b4efb310512527d64266b065de3f894fc52f84efcbf7eaa5965fb" 214 | "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" 215 | "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" 216 | "checksum proc-macro-hack 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2c725b36c99df7af7bf9324e9c999b9e37d92c8f8caf106d82e1d7953218d2d8" 217 | "checksum proc-macro-hack-impl 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2b753ad9ed99dd8efeaa7d2fb8453c8f6bc3e54b97966d35f1bc77ca6865254a" 218 | "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 219 | "checksum pyo3 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a2a41f717227140e625641996fe62547a14e33e48e3b4b7f853d67fe1135dab5" 220 | "checksum pyo3-derive-backend 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f0a5b709390f25e48f80ae71e766f923057f9042615a0d010d0a2930d1410c8" 221 | "checksum pyo3cls 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d6b9c7a5dcb47d51025fff8c92d1835e00da98a62fc76541edd85969880d7" 222 | "checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" 223 | "checksum regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f0a0bcab2fd7d1d7c54fa9eae6f43eddeb9ce2e7352f8518a814a4f65d60c58" 224 | "checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96" 225 | "checksum spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ceac490aa12c567115b40b7b7fceca03a6c9d53d5defea066123debc83c5dc1f" 226 | "checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" 227 | "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" 228 | "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" 229 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 230 | "checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" 231 | "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" 232 | -------------------------------------------------------------------------------- /episode/8/lenrs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lenrs" 3 | version = "0.1.0" 4 | authors = ["Matthias Endler "] 5 | 6 | [dependencies] 7 | 8 | [dependencies.pyo3] 9 | version = "0.4.0" 10 | default-features = false 11 | 12 | [lib] 13 | crate-type = ["cdylib"] 14 | name = "lenrs" 15 | -------------------------------------------------------------------------------- /episode/8/lenrs/lenrs/__init__.py: -------------------------------------------------------------------------------- 1 | from ._lenrs import len 2 | -------------------------------------------------------------------------------- /episode/8/lenrs/setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from setuptools import setup 3 | 4 | try: 5 | from setuptools_rust import Binding, RustExtension 6 | except ImportError: 7 | import subprocess 8 | errno = subprocess.call( 9 | [sys.executable, '-m', 'pip', 'install', 'setuptools-rust']) 10 | if errno: 11 | print("Please install setuptools-rust package") 12 | raise SystemExit(errno) 13 | else: 14 | from setuptools_rust import Binding, RustExtension 15 | 16 | setup_requires = ['setuptools-rust>=0.9.2'] 17 | install_requires = [] 18 | 19 | setup(name='lenrs', 20 | version='0.1', 21 | classifiers=[ 22 | 'License :: OSI Approved :: MIT License', 23 | 'Development Status :: 3 - Alpha', 24 | 'Intended Audience :: Developers', 25 | 'Programming Language :: Python', 26 | 'Programming Language :: Rust', 27 | 'Operating System :: POSIX', 28 | 'Operating System :: MacOS :: MacOS X', 29 | ], 30 | rust_extensions=[ 31 | RustExtension('lenrs._lenrs', 'Cargo.toml', binding=Binding.PyO3)], 32 | packages=['lenrs'], 33 | zip_safe=False) 34 | -------------------------------------------------------------------------------- /episode/8/lenrs/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate pyo3; 2 | 3 | use pyo3::prelude::*; 4 | 5 | #[pymodinit] 6 | fn init(_py: Python, m: &PyModule) -> PyResult<()> { 7 | #[pyfn(m, "len")] 8 | fn len(py: Python, obj: PyObject) -> PyResult { 9 | if let Ok(s) = obj.extract::(py) { 10 | return Ok(s.len().to_object(py)); 11 | } 12 | if let Ok(v) = obj.extract::>(py) { 13 | return Ok(v.len().to_object(py)); 14 | } 15 | Err(PyErr::new::("Not supported")) 16 | } 17 | 18 | Ok(()) 19 | } 20 | -------------------------------------------------------------------------------- /episode/8/thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-rust/show/02d40f1db583c3b6da38c18e313e08bfff6b08e7/episode/8/thumb.jpg -------------------------------------------------------------------------------- /episode/9/README.md: -------------------------------------------------------------------------------- 1 | # Episode 9 - Go Vs Rust - Concurrency And Race Conditions 2 | 3 | [![YouTube video thumbnail](./thumb.jpg)](https://corrode.dev/hello-rust/) 4 | **[▶ Watch now on Youtube!](https://youtu.be/B5xYBrxVSiE)** 5 | 6 | It's hard to put into words what I like about Rust. Easier to show with a quick example about concurrency and borrowing. 7 | Capturing the "Magic of Rust" is not easy. Luckily, I found a simpler way: show how Rust prevents race-conditions and guides us towards a safe, idiomatic solution. We will port a little Go CLI tool to Rust and compare both solutions in terms of safety, error handling, and concurrency patterns. 8 | 9 | 10 | 11 | If you like to get notified about new episodes, [please subscribe to my channel](https://www.youtube.com/hellorust) 😊. 12 | 13 | Keywords: race-conditions, ownership, mutex, concurrency, rayon, golang, rustlang 14 | 15 | ## Things I mentioned during the show 16 | 17 | * Go in Practice book by Matt Butcher and Matt Farina (Manning Publications, 2016): https://www.manning.com/books/go-in-practice 18 | * Go in Practice example source code: https://github.com/Masterminds/go-in-practice 19 | * Golang provides tooling to help detect race conditions. E.g. try `go run --race` (see 'Go in Practice, p.70') 20 | * Explanation of the Rustlang race-condition prevention: https://stackoverflow.com/questions/30559073/cannot-borrow-captured-outer-variable-in-an-fn-closure-as-mutable 21 | * Amazing article about Rustlang iterator patterns by Karol Kuczmarski: http://xion.io/post/code/rust-iter-patterns.html 22 | * for_each method: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.for_each 23 | * [std::sync::Mutex](https://doc.rust-lang.org/std/sync/struct.Mutex.html) 24 | * Golang sync.Waitgroup: https://golang.org/pkg/sync/#WaitGroup 25 | * rayon crate for easy parallelism: https://github.com/rayon-rs/rayon 26 | * partition method: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.partition 27 | 28 | ## Errata and improvements 29 | 30 | It might come as a surprise to you, but every once in a while *even I* make a mistake. 31 | This section covers all improvements made to the code since the epsiode went live. 32 | For an exhaustive list of all changes to the original code, [go here](https://github.com/hello-rust/show/commits/master/episode/9). 33 | Thanks to all contributors! 34 | 35 | * [killercup](https://github.com/killercup) added [an alternative approach](https://github.com/hello-rust/show/pull/45) that creates one hashmap per file and uses Rayon's reduce to unify them. 36 | * [euantorano](https://github.com/euantorano) provided [an alternative Go approach to counting words](https://github.com/hello-rust/show/pull/46). 37 | * [killercup](https://github.com/killercup) added [benchmarks](https://github.com/hello-rust/show/pull/48) because he seems to have too much free time at his hands he was curios and people kept asking. 38 | Here's a quick preview (but also run them on your own machine!): 39 | 40 | ![](https://cdn.rawgit.com/hello-rust/show/a8de0d77a8cb2672fe1f37f4d9251950038b7b50/episode/9/bench/results/ep9-violin.svg) 41 | 42 | 43 | ## Meta 44 | 45 | * Color scheme: JetJet Alternate for VSCode 46 | 47 | 48 | ## Resources and licenses 49 | 50 | * Comedic Boing, A.wav by InspectorJ (www.jshaw.co.uk) of Freesound.org 51 | * Using 188709__setuniman__thoughts-1a37 by Setuniman (https://freesound.org/people/Setuniman/packs/9857/) 52 | * Yay sound by jbeetle https://freesound.org/people/jbeetle/sounds/274510/ 53 | * piano-improv-prog-clumsy-raucous.wav by newagesoup https://freesound.org/people/newagesoup/sounds/341352/ 54 | * [Crayon designed by Terdpongvector](https://www.freepik.com/free-vector/school-stuff-collection_1060700.htm) 55 | 56 | 57 | 58 | ## Support! 59 | 60 | Preparing, recording, and editing an episode takes a substantial amount of time 61 | (around 30 hours total). I do all of this next to my fulltime dayjob. 62 | If you want to show your appreciation and help me keep the content free 63 | for everybody to enjoy, [please consider sponsoring me on 64 | Github](https://github.com/sponsors/mre/) - no matter the amount. ❤️ -------------------------------------------------------------------------------- /episode/9/bench/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bench" 3 | version = "0.1.0" 4 | authors = ["Pascal Hertleif "] 5 | 6 | [dependencies] 7 | 8 | [dev-dependencies] 9 | criterion = "0.2" 10 | 11 | [[bench]] 12 | name = "ep9" 13 | harness = false 14 | -------------------------------------------------------------------------------- /episode/9/bench/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | In episode 9, 4 | different implementations were presented, 5 | comparing a Go implementation to one in Rust. 6 | Afterwards, some people submitted pull requests adding some more variants. 7 | And while it was not the aim of the episode 8 | (which was about how Rust catches data races!) 9 | people have asked about benchmarking the solutions. 10 | 11 | So here it is. 12 | Benchmarks. 13 | This is also a nice showcase for the [criterion] library. 14 | 15 | [criterion]: https://japaric.github.io/criterion.rs/book/ 16 | 17 | ## Setup 18 | 19 | 1. Install Rust 20 | 2. Install Go 21 | 3. Install gnuplot if you want plots 22 | 23 | ## Running the benchmarks 24 | 25 | 1. In this directory, run `cargo bench` (or `cargo bench -- --noplot` if you don't have gnuplot) 26 | 2. Wait for some time (it needs to compile the implementations before executing them) 27 | 3. See CLI output 28 | 4. Also open `target/criterion/report/index.html` which shows pretty graphs 29 | -------------------------------------------------------------------------------- /episode/9/bench/benches/ep9.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate criterion; 3 | 4 | use criterion::{Criterion, Fun}; 5 | use std::path::{Path, PathBuf}; 6 | use std::process::{Command, Stdio}; 7 | 8 | fn ep9(c: &mut Criterion) { 9 | // A list of functions we want to benchmark. Ugly block syntax because I'm 10 | // lazy and this is the easiest way to make it compile stuff right where we 11 | // need it. 12 | let fns = vec![ 13 | Fun::new("rust fixed", |b, _| { 14 | let path = PathBuf::from("../rust/fixed"); 15 | compile_rust(&path); 16 | let bin = path.join("target/release/fixed"); 17 | b.iter(|| run(&bin)) 18 | }), 19 | Fun::new("rust pascal", |b, _| { 20 | let path = PathBuf::from("../rust/pascal"); 21 | compile_rust(&path); 22 | let bin = path.join("target/release/fixed"); 23 | b.iter(|| run(&bin)) 24 | }), 25 | Fun::new("rust union", |b, _| { 26 | let path = PathBuf::from("../rust/union"); 27 | compile_rust(&path); 28 | let bin = path.join("target/release/fixed"); 29 | b.iter(|| run(&bin)) 30 | }), 31 | Fun::new("go fixed", |b, _| { 32 | let path = PathBuf::from("../go"); 33 | compile_go(&path, "fixed.go"); 34 | let bin = path.join("fixed"); 35 | b.iter(|| run(&bin)) 36 | }), 37 | Fun::new("go worker", |b, _| { 38 | let path = PathBuf::from("../go"); 39 | compile_go(&path, "worker.go"); 40 | let bin = path.join("worker"); 41 | b.iter(|| run(&bin)) 42 | }), 43 | ]; 44 | 45 | // Using this, we get a graph comparing all the functions 46 | c.bench_functions("Episode 9", fns, &()); 47 | } 48 | 49 | // Run the functions we just defined as a group 50 | criterion_group!( 51 | name = benches; 52 | config = Criterion::default().sample_size(10); 53 | targets = ep9 54 | ); 55 | criterion_main!(benches); 56 | 57 | fn compile_rust(path: &Path) { 58 | Command::new("cargo") 59 | .arg("build") 60 | .arg("--release") 61 | .arg("--quiet") 62 | .current_dir(path) 63 | .status() 64 | .expect("failed to execute process"); 65 | } 66 | 67 | fn compile_go(path: &Path, name: &str) { 68 | Command::new("go") 69 | .arg("build") 70 | .arg(name) 71 | .current_dir(path) 72 | .status() 73 | .expect("failed to execute process"); 74 | } 75 | 76 | fn run(path: &Path) { 77 | Command::new(path) 78 | .arg("../text/hamlet_gut.txt") 79 | .arg("../text/henry_v_gut.txt") 80 | .arg("../text/macbeth_gut_f.txt") 81 | .arg("../text/romeo_and_juliet_gut.txt") 82 | .stdout(Stdio::null()) 83 | .status() 84 | .expect("failed to execute process"); 85 | } 86 | -------------------------------------------------------------------------------- /episode/9/episode.yml: -------------------------------------------------------------------------------- 1 | number: 9 2 | title: Go vs Rust - Concurrency and Race Conditions 3 | id: B5xYBrxVSiE 4 | intro: It's hard to put into words what I like about Rust. Easier to show with a quick example about concurrency and borrowing. 5 | details: > 6 | Capturing the "Magic of Rust" is not easy. Luckily, I found a simpler 7 | way: show how Rust prevents race-conditions and guides us towards a safe, 8 | idiomatic solution. We will port a little Go CLI tool to Rust and compare 9 | both solutions in terms of safety, error handling, and concurrency patterns. 10 | keywords: 11 | - race-conditions 12 | - ownership 13 | - mutex 14 | - concurrency 15 | - rayon 16 | - golang 17 | - rustlang 18 | notes: 19 | - "Go in Practice book by Matt Butcher and Matt Farina (Manning Publications, 2016): https://www.manning.com/books/go-in-practice" 20 | - "Go in Practice example source code: 21 | https://github.com/Masterminds/go-in-practice" 22 | - "Golang provides tooling to help detect race conditions. E.g. try `go run --race` (see 'Go in Practice, p.70')" 23 | - "Explanation of the Rustlang race-condition prevention: https://stackoverflow.com/questions/30559073/cannot-borrow-captured-outer-variable-in-an-fn-closure-as-mutable" 24 | - "Amazing article about Rustlang iterator patterns by Karol Kuczmarski: 25 | http://xion.io/post/code/rust-iter-patterns.html" 26 | - "for_each method: 27 | https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.for_each" 28 | - "[std::sync::Mutex](https://doc.rust-lang.org/std/sync/struct.Mutex.html)" 29 | - "Golang sync.Waitgroup: https://golang.org/pkg/sync/#WaitGroup" 30 | - "rayon crate for easy parallelism: https://github.com/rayon-rs/rayon" 31 | - "partition method: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.partition" 32 | errata: 33 | - "[killercup](https://github.com/killercup) added [an alternative approach](https://github.com/hello-rust/show/pull/45) that creates one hashmap per file and uses Rayon's reduce to unify them." 34 | - "[euantorano](https://github.com/euantorano) provided [an alternative Go approach to counting words](https://github.com/hello-rust/show/pull/46)." 35 | - | 36 | [killercup](https://github.com/killercup) added [benchmarks](https://github.com/hello-rust/show/pull/48) because he seems to have too much free time at his hands he was curios and people kept asking. 37 | Here's a quick preview (but also run them on your own machine!): 38 | 39 | ![](https://cdn.rawgit.com/hello-rust/show/a8de0d77a8cb2672fe1f37f4d9251950038b7b50/episode/9/bench/results/ep9-violin.svg) 40 | metas: 41 | - "Color scheme: JetJet Alternate for VSCode" 42 | licenses: 43 | - "Comedic Boing, A.wav by InspectorJ (www.jshaw.co.uk) of Freesound.org" 44 | - "Using 188709__setuniman__thoughts-1a37 by Setuniman (https://freesound.org/people/Setuniman/packs/9857/)" 45 | - "Yay sound by jbeetle https://freesound.org/people/jbeetle/sounds/274510/" 46 | - "piano-improv-prog-clumsy-raucous.wav by newagesoup https://freesound.org/people/newagesoup/sounds/341352/" 47 | - "[Crayon designed by Terdpongvector](https://www.freepik.com/free-vector/school-stuff-collection_1060700.htm)" 48 | chapters: 49 | - "1:28 Golang code" 50 | - "5:41 Trying to run our concurrent Go code" 51 | - "8:31 What I don't like about concurrent Go code" 52 | - "9:38 Rust code" 53 | - "15:36 From sequential to concurrent code" 54 | - "23:23 The beauty of Rust" 55 | - "28:05 Handling errors in for_each" 56 | -------------------------------------------------------------------------------- /episode/9/go/.gitignore: -------------------------------------------------------------------------------- 1 | fixed 2 | worker 3 | -------------------------------------------------------------------------------- /episode/9/go/README.md: -------------------------------------------------------------------------------- 1 | Original source: https://github.com/Masterminds/go-in-practice 2 | Slightly modified. 3 | 4 | Usage: 5 | 6 | ``` 7 | go run fixed.go ../text/hamlet_gut.txt ../text/henry_v_gut.txt ../text/macbeth_gut_f.txt ../text/romeo_and_juliet_gut.txt | sort -n 8 | ``` -------------------------------------------------------------------------------- /episode/9/go/fixed.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "strings" 8 | "sync" 9 | ) 10 | 11 | func main() { 12 | var wg sync.WaitGroup 13 | 14 | w := newWords() 15 | for _, f := range os.Args[1:] { 16 | wg.Add(1) 17 | go func(file string) { 18 | if err := tallyWords(file, w); err != nil { 19 | fmt.Println(err.Error()) 20 | } 21 | wg.Done() 22 | }(f) 23 | } 24 | wg.Wait() 25 | 26 | fmt.Println("Words that appear more than once:") 27 | w.Lock() 28 | for word, count := range w.found { 29 | if count > 1 { 30 | fmt.Printf("%d %s\n", count, word) 31 | } 32 | } 33 | w.Unlock() 34 | } 35 | 36 | type words struct { 37 | sync.Mutex 38 | found map[string]int 39 | } 40 | 41 | func newWords() *words { 42 | return &words{found: map[string]int{}} 43 | } 44 | 45 | func (w *words) add(word string, n int) { 46 | w.Lock() 47 | defer w.Unlock() 48 | count, ok := w.found[word] 49 | if !ok { 50 | w.found[word] = n 51 | return 52 | } 53 | w.found[word] = count + n 54 | } 55 | 56 | func tallyWords(filename string, dict *words) error { 57 | file, err := os.Open(filename) 58 | if err != nil { 59 | return err 60 | } 61 | defer file.Close() 62 | 63 | scanner := bufio.NewScanner(file) 64 | scanner.Split(bufio.ScanWords) 65 | for scanner.Scan() { 66 | word := strings.ToLower(scanner.Text()) 67 | dict.add(word, 1) 68 | } 69 | return scanner.Err() 70 | } 71 | -------------------------------------------------------------------------------- /episode/9/go/race.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "strings" 8 | "sync" 9 | ) 10 | 11 | func main() { 12 | var wg sync.WaitGroup 13 | 14 | w := newWords() 15 | for _, f := range os.Args[1:] { 16 | wg.Add(1) 17 | go func(file string) { 18 | if err := tallyWords(file, w); err != nil { 19 | fmt.Println(err.Error()) 20 | } 21 | wg.Done() 22 | }(f) 23 | } 24 | wg.Wait() 25 | 26 | fmt.Println("Words that appear more than once:") 27 | for word, count := range w.found { 28 | if count > 1 { 29 | fmt.Printf("%d %s\n", count, word) 30 | } 31 | } 32 | } 33 | 34 | type words struct { 35 | found map[string]int 36 | } 37 | 38 | func newWords() *words { 39 | return &words{found: map[string]int{}} 40 | } 41 | 42 | func (w *words) add(word string, n int) { 43 | count, ok := w.found[word] 44 | if !ok { 45 | w.found[word] = n 46 | return 47 | } 48 | w.found[word] = count + n 49 | } 50 | 51 | func tallyWords(filename string, dict *words) error { 52 | file, err := os.Open(filename) 53 | if err != nil { 54 | return err 55 | } 56 | defer file.Close() 57 | 58 | scanner := bufio.NewScanner(file) 59 | scanner.Split(bufio.ScanWords) 60 | for scanner.Scan() { 61 | word := strings.ToLower(scanner.Text()) 62 | dict.add(word, 1) 63 | } 64 | return scanner.Err() 65 | } 66 | -------------------------------------------------------------------------------- /episode/9/go/worker.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "runtime" 8 | "strings" 9 | "sync" 10 | ) 11 | 12 | func worker(tasks <-chan string, wordsChan chan<- string, errorsChan chan<- error, wg *sync.WaitGroup) { 13 | defer wg.Done() 14 | 15 | for filename := range tasks { 16 | file, err := os.Open(filename) 17 | 18 | if err != nil { 19 | errorsChan <- err 20 | continue 21 | } 22 | 23 | scanner := bufio.NewScanner(file) 24 | scanner.Split(bufio.ScanWords) 25 | 26 | for scanner.Scan() { 27 | word := strings.ToLower(scanner.Text()) 28 | 29 | wordsChan <- word 30 | } 31 | 32 | if scanner.Err() != nil { 33 | errorsChan <- scanner.Err() 34 | } 35 | 36 | file.Close() 37 | } 38 | } 39 | 40 | func collectResults(m map[string]int, wordsChan <-chan string, errorsChan <-chan error) { 41 | for { 42 | select { 43 | case word := <-wordsChan: 44 | if _, ok := m[word]; ok { 45 | m[word] += 1 46 | } else { 47 | m[word] = 1 48 | } 49 | case err := <-errorsChan: 50 | fmt.Fprintf(os.Stderr, "Error: %s\n", err) 51 | } 52 | } 53 | } 54 | 55 | func main() { 56 | numArgs := len(os.Args) - 1 57 | 58 | jobs := make(chan string, numArgs) 59 | wordsChan := make(chan string, numArgs) 60 | errorsChan := make(chan error, numArgs) 61 | 62 | wordCount := make(map[string]int) 63 | var wg sync.WaitGroup 64 | 65 | wg.Add(runtime.NumCPU()) 66 | 67 | for w := 0; w < runtime.NumCPU(); w++ { 68 | go worker(jobs, wordsChan, errorsChan, &wg) 69 | } 70 | 71 | go collectResults(wordCount, wordsChan, errorsChan) 72 | 73 | for _, f := range os.Args[1:] { 74 | jobs <- f 75 | } 76 | 77 | close(jobs) 78 | 79 | wg.Wait() 80 | 81 | fmt.Println("Words that appear more than once:") 82 | for word, count := range wordCount { 83 | if count > 1 { 84 | fmt.Printf("%d %s\n", count, word) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /episode/9/rust/fixed/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "arrayvec" 3 | version = "0.4.7" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "cfg-if" 11 | version = "0.1.5" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | 14 | [[package]] 15 | name = "crossbeam-deque" 16 | version = "0.2.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | dependencies = [ 19 | "crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 20 | "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 21 | ] 22 | 23 | [[package]] 24 | name = "crossbeam-epoch" 25 | version = "0.3.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | dependencies = [ 28 | "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", 29 | "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 30 | "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 31 | "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 32 | "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 33 | "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 34 | "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 35 | ] 36 | 37 | [[package]] 38 | name = "crossbeam-utils" 39 | version = "0.2.2" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | dependencies = [ 42 | "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 43 | ] 44 | 45 | [[package]] 46 | name = "either" 47 | version = "1.5.0" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | 50 | [[package]] 51 | name = "fixed" 52 | version = "0.1.0" 53 | dependencies = [ 54 | "rayon 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 55 | ] 56 | 57 | [[package]] 58 | name = "lazy_static" 59 | version = "1.1.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | dependencies = [ 62 | "version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 63 | ] 64 | 65 | [[package]] 66 | name = "libc" 67 | version = "0.2.43" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | 70 | [[package]] 71 | name = "memoffset" 72 | version = "0.2.1" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | 75 | [[package]] 76 | name = "nodrop" 77 | version = "0.1.12" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | 80 | [[package]] 81 | name = "num_cpus" 82 | version = "1.8.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | dependencies = [ 85 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 86 | ] 87 | 88 | [[package]] 89 | name = "rayon" 90 | version = "1.0.2" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | dependencies = [ 93 | "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 94 | "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 95 | "rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 96 | ] 97 | 98 | [[package]] 99 | name = "rayon-core" 100 | version = "1.4.1" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | dependencies = [ 103 | "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 104 | "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 105 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 106 | "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 107 | ] 108 | 109 | [[package]] 110 | name = "scopeguard" 111 | version = "0.3.3" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | 114 | [[package]] 115 | name = "version_check" 116 | version = "0.1.4" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | 119 | [metadata] 120 | "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" 121 | "checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" 122 | "checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3" 123 | "checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150" 124 | "checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9" 125 | "checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" 126 | "checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7" 127 | "checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" 128 | "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" 129 | "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" 130 | "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" 131 | "checksum rayon 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "df7a791f788cb4c516f0e091301a29c2b71ef680db5e644a7d68835c8ae6dbfa" 132 | "checksum rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b055d1e92aba6877574d8fe604a63c8b5df60f60e5982bf7ccbb1338ea527356" 133 | "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" 134 | "checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051" 135 | -------------------------------------------------------------------------------- /episode/9/rust/fixed/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fixed" 3 | version = "0.1.0" 4 | authors = ["Matthias Endler "] 5 | 6 | [dependencies] 7 | rayon = "1.0.2" 8 | -------------------------------------------------------------------------------- /episode/9/rust/fixed/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate rayon; 2 | 3 | use rayon::prelude::*; 4 | use std::collections::HashMap; 5 | use std::env; 6 | use std::fs; 7 | use std::sync::Mutex; 8 | 9 | #[derive(Debug)] 10 | enum Error { 11 | Lock, 12 | } 13 | 14 | type Words = Mutex>; 15 | 16 | fn main() -> Result<(), Box> { 17 | let w = Words::new(HashMap::new()); 18 | env::args() 19 | .skip(1) 20 | .collect::>() 21 | .par_iter() 22 | .for_each(|arg| tally_words(arg.to_string(), &w).unwrap()); 23 | 24 | let words = w.lock().map_err(|_| Error::Lock)?; 25 | for (word, count) in words.iter() { 26 | if *count > 1 { 27 | println!("{} {}", count, word) 28 | } 29 | } 30 | 31 | Ok(()) 32 | } 33 | 34 | fn tally_words(filename: String, w: &Words) -> Result<(), Box> { 35 | let contents = fs::read_to_string(filename).expect("Unable to read file"); 36 | 37 | for s in contents.split_whitespace() { 38 | let key = s.to_lowercase(); 39 | { 40 | let mut map = w.lock().map_err(|_| Error::Lock)?; 41 | *map.entry(key).or_insert(0) += 1; 42 | } 43 | } 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /episode/9/rust/pascal/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "ansi_term" 3 | version = "0.11.0" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "arrayvec" 11 | version = "0.4.7" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | dependencies = [ 14 | "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 15 | ] 16 | 17 | [[package]] 18 | name = "atty" 19 | version = "0.2.11" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | dependencies = [ 22 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 23 | "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 24 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 25 | ] 26 | 27 | [[package]] 28 | name = "bitflags" 29 | version = "1.0.4" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | 32 | [[package]] 33 | name = "cfg-if" 34 | version = "0.1.5" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | 37 | [[package]] 38 | name = "clap" 39 | version = "2.32.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | dependencies = [ 42 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 43 | "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 45 | "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 47 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 48 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 49 | ] 50 | 51 | [[package]] 52 | name = "crossbeam-deque" 53 | version = "0.2.0" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | dependencies = [ 56 | "crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 57 | "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 58 | ] 59 | 60 | [[package]] 61 | name = "crossbeam-epoch" 62 | version = "0.3.1" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | dependencies = [ 65 | "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", 66 | "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 67 | "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 68 | "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 69 | "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 70 | "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 71 | "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 72 | ] 73 | 74 | [[package]] 75 | name = "crossbeam-utils" 76 | version = "0.2.2" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | dependencies = [ 79 | "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 80 | ] 81 | 82 | [[package]] 83 | name = "either" 84 | version = "1.5.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | 87 | [[package]] 88 | name = "fixed" 89 | version = "0.1.0" 90 | dependencies = [ 91 | "im 12.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 92 | "rayon 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 93 | "structopt 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", 94 | ] 95 | 96 | [[package]] 97 | name = "im" 98 | version = "12.0.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | dependencies = [ 101 | "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 102 | "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 103 | ] 104 | 105 | [[package]] 106 | name = "lazy_static" 107 | version = "1.1.0" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | dependencies = [ 110 | "version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 111 | ] 112 | 113 | [[package]] 114 | name = "libc" 115 | version = "0.2.43" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | 118 | [[package]] 119 | name = "memoffset" 120 | version = "0.2.1" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | 123 | [[package]] 124 | name = "nodrop" 125 | version = "0.1.12" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | 128 | [[package]] 129 | name = "num_cpus" 130 | version = "1.8.0" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | dependencies = [ 133 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 134 | ] 135 | 136 | [[package]] 137 | name = "proc-macro2" 138 | version = "0.4.16" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | dependencies = [ 141 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 142 | ] 143 | 144 | [[package]] 145 | name = "quote" 146 | version = "0.6.8" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | dependencies = [ 149 | "proc-macro2 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)", 150 | ] 151 | 152 | [[package]] 153 | name = "rayon" 154 | version = "1.0.2" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | dependencies = [ 157 | "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 158 | "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 159 | "rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 160 | ] 161 | 162 | [[package]] 163 | name = "rayon-core" 164 | version = "1.4.1" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | dependencies = [ 167 | "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 168 | "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 169 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 170 | "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 171 | ] 172 | 173 | [[package]] 174 | name = "redox_syscall" 175 | version = "0.1.40" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | 178 | [[package]] 179 | name = "redox_termios" 180 | version = "0.1.1" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | dependencies = [ 183 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 184 | ] 185 | 186 | [[package]] 187 | name = "rustc_version" 188 | version = "0.2.3" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | dependencies = [ 191 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 192 | ] 193 | 194 | [[package]] 195 | name = "scopeguard" 196 | version = "0.3.3" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | 199 | [[package]] 200 | name = "semver" 201 | version = "0.9.0" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | dependencies = [ 204 | "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 205 | ] 206 | 207 | [[package]] 208 | name = "semver-parser" 209 | version = "0.7.0" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | 212 | [[package]] 213 | name = "strsim" 214 | version = "0.7.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | 217 | [[package]] 218 | name = "structopt" 219 | version = "0.2.10" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | dependencies = [ 222 | "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", 223 | "structopt-derive 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", 224 | ] 225 | 226 | [[package]] 227 | name = "structopt-derive" 228 | version = "0.2.10" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | dependencies = [ 231 | "proc-macro2 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)", 232 | "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", 233 | "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", 234 | ] 235 | 236 | [[package]] 237 | name = "syn" 238 | version = "0.14.9" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | dependencies = [ 241 | "proc-macro2 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)", 242 | "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", 243 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 244 | ] 245 | 246 | [[package]] 247 | name = "termion" 248 | version = "1.5.1" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | dependencies = [ 251 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 252 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 253 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 254 | ] 255 | 256 | [[package]] 257 | name = "textwrap" 258 | version = "0.10.0" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | dependencies = [ 261 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 262 | ] 263 | 264 | [[package]] 265 | name = "typenum" 266 | version = "1.10.0" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | 269 | [[package]] 270 | name = "unicode-width" 271 | version = "0.1.5" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | 274 | [[package]] 275 | name = "unicode-xid" 276 | version = "0.1.0" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | 279 | [[package]] 280 | name = "vec_map" 281 | version = "0.8.1" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | 284 | [[package]] 285 | name = "version_check" 286 | version = "0.1.4" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | 289 | [[package]] 290 | name = "winapi" 291 | version = "0.3.5" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | dependencies = [ 294 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 295 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 296 | ] 297 | 298 | [[package]] 299 | name = "winapi-i686-pc-windows-gnu" 300 | version = "0.4.0" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | 303 | [[package]] 304 | name = "winapi-x86_64-pc-windows-gnu" 305 | version = "0.4.0" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | 308 | [metadata] 309 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 310 | "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" 311 | "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" 312 | "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" 313 | "checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" 314 | "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" 315 | "checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3" 316 | "checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150" 317 | "checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9" 318 | "checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" 319 | "checksum im 12.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ca4b379f0383e3a502dfdd6a9424fd9707633160d5215bb42d7e00db8a056ab" 320 | "checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7" 321 | "checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" 322 | "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" 323 | "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" 324 | "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" 325 | "checksum proc-macro2 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)" = "37460f858ac0db19bceb2585494de611c9d8618062e6da2994a211b600cc4fc9" 326 | "checksum quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dd636425967c33af890042c483632d33fa7a18f19ad1d7ea72e8998c6ef8dea5" 327 | "checksum rayon 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "df7a791f788cb4c516f0e091301a29c2b71ef680db5e644a7d68835c8ae6dbfa" 328 | "checksum rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b055d1e92aba6877574d8fe604a63c8b5df60f60e5982bf7ccbb1338ea527356" 329 | "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" 330 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 331 | "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 332 | "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" 333 | "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 334 | "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 335 | "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" 336 | "checksum structopt 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8e9ad6a11096cbecdcca0cc6aa403fdfdbaeda2fb3323a39c98e6a166a1e45a" 337 | "checksum structopt-derive 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4cbce8ccdc62166bd594c14396a3242bf94c337a51dbfa9be1076dd74b3db2af" 338 | "checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" 339 | "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" 340 | "checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" 341 | "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" 342 | "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" 343 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 344 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 345 | "checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051" 346 | "checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd" 347 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 348 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 349 | -------------------------------------------------------------------------------- /episode/9/rust/pascal/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fixed" 3 | version = "0.1.0" 4 | authors = ["Matthias Endler "] 5 | 6 | [dependencies] 7 | rayon = "1.0.2" 8 | structopt = "0.2.10" 9 | im = "12.0.0" 10 | -------------------------------------------------------------------------------- /episode/9/rust/pascal/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate rayon; 2 | #[macro_use] 3 | extern crate structopt; 4 | extern crate im; 5 | 6 | use im::HashMap; 7 | use rayon::prelude::*; 8 | use std::error::Error; 9 | use std::fs; 10 | use std::path::{Path, PathBuf}; 11 | use structopt::StructOpt; 12 | 13 | #[derive(StructOpt)] 14 | struct Cli { 15 | #[structopt(name = "FILE", parse(from_os_str))] 16 | files: Vec, 17 | } 18 | 19 | type Words = HashMap; 20 | 21 | fn main() -> Result<(), Box> { 22 | let args = Cli::from_args(); 23 | 24 | let words = args 25 | .files 26 | .par_iter() 27 | .map(|filename| { 28 | tally_words(filename) 29 | .map_err(|e| eprintln!("Error processing {}: {:?}", filename.display(), e)) 30 | .unwrap_or_default() 31 | }).reduce( 32 | || Words::new(), 33 | |result, current| current.union_with(result, |a, b| a + b), 34 | ); 35 | 36 | for (word, count) in &words { 37 | if *count > 1 { 38 | println!("{} {}", count, word) 39 | } 40 | } 41 | 42 | Ok(()) 43 | } 44 | 45 | fn tally_words(filename: &Path) -> Result> { 46 | let mut words = Words::new(); 47 | let contents = fs::read_to_string(filename)?; 48 | 49 | for s in contents.split_whitespace() { 50 | let key = s.to_lowercase(); 51 | *words.entry(key).or_insert(0) += 1; 52 | } 53 | Ok(words) 54 | } 55 | -------------------------------------------------------------------------------- /episode/9/rust/race/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "arrayvec" 3 | version = "0.4.7" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "cfg-if" 11 | version = "0.1.5" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | 14 | [[package]] 15 | name = "crossbeam-deque" 16 | version = "0.2.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | dependencies = [ 19 | "crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 20 | "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 21 | ] 22 | 23 | [[package]] 24 | name = "crossbeam-epoch" 25 | version = "0.3.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | dependencies = [ 28 | "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", 29 | "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 30 | "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 31 | "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 32 | "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 33 | "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 34 | "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 35 | ] 36 | 37 | [[package]] 38 | name = "crossbeam-utils" 39 | version = "0.2.2" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | dependencies = [ 42 | "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 43 | ] 44 | 45 | [[package]] 46 | name = "either" 47 | version = "1.5.0" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | 50 | [[package]] 51 | name = "lazy_static" 52 | version = "1.1.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | dependencies = [ 55 | "version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 56 | ] 57 | 58 | [[package]] 59 | name = "libc" 60 | version = "0.2.43" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | 63 | [[package]] 64 | name = "memoffset" 65 | version = "0.2.1" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | 68 | [[package]] 69 | name = "nodrop" 70 | version = "0.1.12" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | 73 | [[package]] 74 | name = "num_cpus" 75 | version = "1.8.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | dependencies = [ 78 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 79 | ] 80 | 81 | [[package]] 82 | name = "race" 83 | version = "0.1.0" 84 | dependencies = [ 85 | "rayon 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 86 | ] 87 | 88 | [[package]] 89 | name = "rayon" 90 | version = "1.0.2" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | dependencies = [ 93 | "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 94 | "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 95 | "rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 96 | ] 97 | 98 | [[package]] 99 | name = "rayon-core" 100 | version = "1.4.1" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | dependencies = [ 103 | "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 104 | "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 105 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 106 | "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 107 | ] 108 | 109 | [[package]] 110 | name = "scopeguard" 111 | version = "0.3.3" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | 114 | [[package]] 115 | name = "version_check" 116 | version = "0.1.4" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | 119 | [metadata] 120 | "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" 121 | "checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" 122 | "checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3" 123 | "checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150" 124 | "checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9" 125 | "checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" 126 | "checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7" 127 | "checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" 128 | "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" 129 | "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" 130 | "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" 131 | "checksum rayon 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "df7a791f788cb4c516f0e091301a29c2b71ef680db5e644a7d68835c8ae6dbfa" 132 | "checksum rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b055d1e92aba6877574d8fe604a63c8b5df60f60e5982bf7ccbb1338ea527356" 133 | "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" 134 | "checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051" 135 | -------------------------------------------------------------------------------- /episode/9/rust/race/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "race" 3 | version = "0.1.0" 4 | authors = ["Matthias Endler "] 5 | 6 | [dependencies] 7 | rayon = "1.0.2" 8 | -------------------------------------------------------------------------------- /episode/9/rust/race/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate rayon; 2 | 3 | use rayon::prelude::*; 4 | use std::collections::HashMap; 5 | use std::env; 6 | use std::fs; 7 | 8 | #[derive(Debug)] 9 | enum Error { 10 | Lock, 11 | } 12 | 13 | type Words = HashMap; 14 | 15 | fn main() -> Result<(), Box> { 16 | let mut words = Words::new(); 17 | env::args() 18 | .skip(1) 19 | .collect::>() 20 | .par_iter() // iter() works 21 | .for_each(|arg| tally_words(arg.to_string(), &mut words).unwrap()); 22 | 23 | for (word, count) in words.iter() { 24 | if *count > 1 { 25 | println!("{} {}", count, word) 26 | } 27 | } 28 | 29 | Ok(()) 30 | } 31 | 32 | fn tally_words(filename: String, w: &mut Words) -> Result<(), Box> { 33 | let contents = fs::read_to_string(filename).expect("Unable to read file"); 34 | 35 | for s in contents.split_whitespace() { 36 | let key = s.to_lowercase(); 37 | *w.entry(key).or_insert(0) += 1; 38 | } 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /episode/9/rust/union/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "ansi_term" 3 | version = "0.11.0" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "arrayvec" 11 | version = "0.4.7" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | dependencies = [ 14 | "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 15 | ] 16 | 17 | [[package]] 18 | name = "atty" 19 | version = "0.2.11" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | dependencies = [ 22 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 23 | "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 24 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 25 | ] 26 | 27 | [[package]] 28 | name = "bitflags" 29 | version = "1.0.4" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | 32 | [[package]] 33 | name = "cfg-if" 34 | version = "0.1.5" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | 37 | [[package]] 38 | name = "clap" 39 | version = "2.32.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | dependencies = [ 42 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 43 | "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 45 | "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 47 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 48 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 49 | ] 50 | 51 | [[package]] 52 | name = "crossbeam-deque" 53 | version = "0.2.0" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | dependencies = [ 56 | "crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 57 | "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 58 | ] 59 | 60 | [[package]] 61 | name = "crossbeam-epoch" 62 | version = "0.3.1" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | dependencies = [ 65 | "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", 66 | "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 67 | "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 68 | "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 69 | "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 70 | "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 71 | "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 72 | ] 73 | 74 | [[package]] 75 | name = "crossbeam-utils" 76 | version = "0.2.2" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | dependencies = [ 79 | "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 80 | ] 81 | 82 | [[package]] 83 | name = "either" 84 | version = "1.5.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | 87 | [[package]] 88 | name = "fixed" 89 | version = "0.1.0" 90 | dependencies = [ 91 | "rayon 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 92 | "structopt 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", 93 | ] 94 | 95 | [[package]] 96 | name = "lazy_static" 97 | version = "1.1.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | dependencies = [ 100 | "version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 101 | ] 102 | 103 | [[package]] 104 | name = "libc" 105 | version = "0.2.43" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | 108 | [[package]] 109 | name = "memoffset" 110 | version = "0.2.1" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | 113 | [[package]] 114 | name = "nodrop" 115 | version = "0.1.12" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | 118 | [[package]] 119 | name = "num_cpus" 120 | version = "1.8.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | dependencies = [ 123 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 124 | ] 125 | 126 | [[package]] 127 | name = "proc-macro2" 128 | version = "0.4.16" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | dependencies = [ 131 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 132 | ] 133 | 134 | [[package]] 135 | name = "quote" 136 | version = "0.6.8" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | dependencies = [ 139 | "proc-macro2 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)", 140 | ] 141 | 142 | [[package]] 143 | name = "rayon" 144 | version = "1.0.2" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | dependencies = [ 147 | "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 148 | "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 149 | "rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 150 | ] 151 | 152 | [[package]] 153 | name = "rayon-core" 154 | version = "1.4.1" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | dependencies = [ 157 | "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 158 | "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 159 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 160 | "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 161 | ] 162 | 163 | [[package]] 164 | name = "redox_syscall" 165 | version = "0.1.40" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | 168 | [[package]] 169 | name = "redox_termios" 170 | version = "0.1.1" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | dependencies = [ 173 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 174 | ] 175 | 176 | [[package]] 177 | name = "scopeguard" 178 | version = "0.3.3" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | 181 | [[package]] 182 | name = "strsim" 183 | version = "0.7.0" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | 186 | [[package]] 187 | name = "structopt" 188 | version = "0.2.10" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | dependencies = [ 191 | "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", 192 | "structopt-derive 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", 193 | ] 194 | 195 | [[package]] 196 | name = "structopt-derive" 197 | version = "0.2.10" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | dependencies = [ 200 | "proc-macro2 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)", 201 | "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", 202 | "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", 203 | ] 204 | 205 | [[package]] 206 | name = "syn" 207 | version = "0.14.9" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | dependencies = [ 210 | "proc-macro2 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)", 211 | "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", 212 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 213 | ] 214 | 215 | [[package]] 216 | name = "termion" 217 | version = "1.5.1" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | dependencies = [ 220 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 221 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 222 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 223 | ] 224 | 225 | [[package]] 226 | name = "textwrap" 227 | version = "0.10.0" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | dependencies = [ 230 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 231 | ] 232 | 233 | [[package]] 234 | name = "unicode-width" 235 | version = "0.1.5" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | 238 | [[package]] 239 | name = "unicode-xid" 240 | version = "0.1.0" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | 243 | [[package]] 244 | name = "vec_map" 245 | version = "0.8.1" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | 248 | [[package]] 249 | name = "version_check" 250 | version = "0.1.4" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | 253 | [[package]] 254 | name = "winapi" 255 | version = "0.3.5" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | dependencies = [ 258 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 259 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 260 | ] 261 | 262 | [[package]] 263 | name = "winapi-i686-pc-windows-gnu" 264 | version = "0.4.0" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | 267 | [[package]] 268 | name = "winapi-x86_64-pc-windows-gnu" 269 | version = "0.4.0" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | 272 | [metadata] 273 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 274 | "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" 275 | "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" 276 | "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" 277 | "checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" 278 | "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" 279 | "checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3" 280 | "checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150" 281 | "checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9" 282 | "checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" 283 | "checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7" 284 | "checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" 285 | "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" 286 | "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" 287 | "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" 288 | "checksum proc-macro2 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)" = "37460f858ac0db19bceb2585494de611c9d8618062e6da2994a211b600cc4fc9" 289 | "checksum quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dd636425967c33af890042c483632d33fa7a18f19ad1d7ea72e8998c6ef8dea5" 290 | "checksum rayon 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "df7a791f788cb4c516f0e091301a29c2b71ef680db5e644a7d68835c8ae6dbfa" 291 | "checksum rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b055d1e92aba6877574d8fe604a63c8b5df60f60e5982bf7ccbb1338ea527356" 292 | "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" 293 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 294 | "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" 295 | "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" 296 | "checksum structopt 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8e9ad6a11096cbecdcca0cc6aa403fdfdbaeda2fb3323a39c98e6a166a1e45a" 297 | "checksum structopt-derive 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4cbce8ccdc62166bd594c14396a3242bf94c337a51dbfa9be1076dd74b3db2af" 298 | "checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" 299 | "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" 300 | "checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" 301 | "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" 302 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 303 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 304 | "checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051" 305 | "checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd" 306 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 307 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 308 | -------------------------------------------------------------------------------- /episode/9/rust/union/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fixed" 3 | version = "0.1.0" 4 | authors = ["Matthias Endler "] 5 | 6 | [dependencies] 7 | rayon = "1.0.2" 8 | structopt = "0.2.10" 9 | -------------------------------------------------------------------------------- /episode/9/rust/union/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate rayon; 2 | 3 | use rayon::prelude::*; 4 | use std::collections::HashMap; 5 | use std::env; 6 | use std::error::Error; 7 | use std::fs; 8 | 9 | type Words = HashMap; 10 | 11 | fn main() -> Result<(), Box> { 12 | let words = env::args() 13 | .skip(1) 14 | .collect::>() 15 | .par_iter() 16 | .map(|arg| { 17 | tally_words(arg) 18 | .map_err(|e| eprintln!("Error processing {}: {:?}", arg, e)) 19 | .unwrap_or_default() 20 | }).reduce( 21 | || Words::new(), 22 | |mut result, current| { 23 | for (key, val) in current { 24 | result.entry(key).and_modify(|e| *e += val).or_insert(val); 25 | } 26 | result 27 | }, 28 | ); 29 | 30 | for (word, count) in words.iter() { 31 | if *count > 1 { 32 | println!("{} {}", count, word) 33 | } 34 | } 35 | 36 | Ok(()) 37 | } 38 | 39 | fn tally_words(filename: &str) -> Result> { 40 | let mut words = Words::new(); 41 | let contents = fs::read_to_string(filename)?; 42 | 43 | for s in contents.split_whitespace() { 44 | let key = s.to_lowercase(); 45 | *words.entry(key).or_insert(0) += 1; 46 | } 47 | Ok(words) 48 | } 49 | -------------------------------------------------------------------------------- /episode/9/thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-rust/show/02d40f1db583c3b6da38c18e313e08bfff6b08e7/episode/9/thumb.jpg -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hello-rust/show/02d40f1db583c3b6da38c18e313e08bfff6b08e7/logo.png -------------------------------------------------------------------------------- /release/youtube/.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | -------------------------------------------------------------------------------- /release/youtube/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.10-alpine AS build 2 | LABEL maintainer="Matthias Endler " 3 | RUN apk update && apk upgrade && \ 4 | apk add --no-cache git 5 | WORKDIR /go/src/github.com/hello-rust/show/ 6 | COPY . . 7 | #RUN go get golang.org/s/gogetcmd 8 | #RUN go get golang.org/x/net/context 9 | RUN go get -u github.com/golang/dep/cmd/dep 10 | RUN dep init 11 | RUN dep ensure 12 | RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o /bin/app 13 | 14 | FROM alpine:latest 15 | RUN apk add --no-cache ca-certificates 16 | COPY --from=build /bin/app . 17 | CMD ["./app"] % -------------------------------------------------------------------------------- /release/youtube/main.go: -------------------------------------------------------------------------------- 1 | // https://developers.google.com/youtube/v3/docs/videos/update 2 | 3 | package main 4 | 5 | import ( 6 | "bufio" 7 | "encoding/json" 8 | "errors" 9 | "flag" 10 | "fmt" 11 | "io/ioutil" 12 | "log" 13 | "net" 14 | "net/http" 15 | "os" 16 | "os/exec" 17 | "path/filepath" 18 | "runtime" 19 | "strconv" 20 | "strings" 21 | "youtube" 22 | 23 | "golang.org/x/net/context" 24 | "golang.org/x/oauth2" 25 | ) 26 | 27 | const missingClientSecretsMessage = ` 28 | Please configure OAuth 2.0 29 | ` 30 | 31 | var ( 32 | clientSecretsFile = flag.String("secrets", "cs.json", "Client Secrets configuration") 33 | cacheFile = flag.String("cache", "request.token", "Token cache file") 34 | ) 35 | 36 | // ClientConfig is a data structure definition for the client_secrets.json file. 37 | // The code unmarshals the JSON configuration file into this structure. 38 | type ClientConfig struct { 39 | ClientID string `json:"client_id"` 40 | ClientSecret string `json:"client_secret"` 41 | RedirectURIs []string `json:"redirect_uris"` 42 | AuthURI string `json:"auth_uri"` 43 | TokenURI string `json:"token_uri"` 44 | } 45 | 46 | // Config is a root-level configuration object. 47 | type Config struct { 48 | Installed ClientConfig `json:"installed"` 49 | Web ClientConfig `json:"web"` 50 | } 51 | 52 | // openURL opens a browser window to the specified location. 53 | // This code originally appeared at: 54 | // http://stackoverflow.com/questions/10377243/how-can-i-launch-a-process-that-is-not-a-file-in-go 55 | func openURL(url string) error { 56 | var err error 57 | switch runtime.GOOS { 58 | case "linux": 59 | err = exec.Command("xdg-open", url).Start() 60 | case "windows": 61 | err = exec.Command("rundll32", "url.dll,FileProtocolHandler", "http://localhost:4001/").Start() 62 | case "darwin": 63 | err = exec.Command("open", url).Start() 64 | default: 65 | err = fmt.Errorf("Cannot open URL %s on this platform", url) 66 | } 67 | return err 68 | } 69 | 70 | // readConfig reads the configuration from clientSecretsFile. 71 | // It returns an oauth configuration object for use with the Google API client. 72 | func readConfig(scope string) (*oauth2.Config, error) { 73 | // Read the secrets file 74 | data, err := ioutil.ReadFile(*clientSecretsFile) 75 | if err != nil { 76 | pwd, _ := os.Getwd() 77 | fullPath := filepath.Join(pwd, *clientSecretsFile) 78 | return nil, fmt.Errorf(missingClientSecretsMessage, fullPath) 79 | } 80 | 81 | cfg := new(Config) 82 | err = json.Unmarshal(data, &cfg) 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | var redirectUri string 88 | if len(cfg.Web.RedirectURIs) > 0 { 89 | redirectUri = cfg.Web.RedirectURIs[0] 90 | } else if len(cfg.Installed.RedirectURIs) > 0 { 91 | redirectUri = cfg.Installed.RedirectURIs[0] 92 | } else { 93 | return nil, errors.New("Must specify a redirect URI in config file or when creating OAuth client") 94 | } 95 | 96 | return &oauth2.Config{ 97 | ClientID: cfg.Installed.ClientID, 98 | ClientSecret: cfg.Installed.ClientSecret, 99 | Scopes: []string{scope}, 100 | Endpoint: oauth2.Endpoint{cfg.Installed.AuthURI, cfg.Installed.TokenURI}, 101 | RedirectURL: redirectUri, 102 | }, nil 103 | } 104 | 105 | // Start a web server that listens on http://localhost:8080. 106 | // The webserver waits for an oauth code in the three-legged auth flow. 107 | func startWebServer() (codeCh chan string, err error) { 108 | listener, err := net.Listen("tcp", "localhost:8080") 109 | if err != nil { 110 | return nil, err 111 | } 112 | codeCh = make(chan string) 113 | go http.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 114 | code := r.FormValue("code") 115 | codeCh <- code // send code to OAuth flow 116 | listener.Close() 117 | w.Header().Set("Content-Type", "text/plain") 118 | fmt.Fprintf(w, "Received code: %v\r\nYou can now safely close this browser window.", code) 119 | })) 120 | 121 | return codeCh, nil 122 | } 123 | 124 | // buildOAuthHTTPClient takes the user through the three-legged OAuth flow. 125 | // It opens a browser in the native OS or outputs a URL, then blocks until 126 | // the redirect completes to the /oauth2callback URI. 127 | // It returns an instance of an HTTP client that can be passed to the 128 | // constructor of the API client. 129 | func buildOAuthHTTPClient(scope string) (*http.Client, error) { 130 | config, err := readConfig(scope) 131 | if err != nil { 132 | msg := fmt.Sprintf("Cannot read configuration file: %v", err) 133 | return nil, errors.New(msg) 134 | } 135 | 136 | var ctx context.Context 137 | 138 | // Try to read the token from the cache file. 139 | // If an error occurs, do the three-legged OAuth flow because 140 | // the token is invalid or doesn't exist. 141 | var token *oauth2.Token 142 | 143 | data, err := ioutil.ReadFile(*cacheFile) 144 | if err == nil { 145 | err = json.Unmarshal(data, &token) 146 | } 147 | if (err != nil) || !token.Valid() { 148 | // Start web server. 149 | // This is how this program receives the authorization code 150 | // when the browser redirects. 151 | codeCh, err := startWebServer() 152 | if err != nil { 153 | return nil, err 154 | } 155 | fmt.Println(codeCh) 156 | 157 | // Open url in browser 158 | url := config.AuthCodeURL("") 159 | err = openURL(url) 160 | if err != nil { 161 | fmt.Println("Visit the URL below to get a code.", 162 | " This program will pause until the site is visted.") 163 | } else { 164 | fmt.Println("Your browser has been opened to an authorization URL.", 165 | " This program will resume once authorization has been provided.\n") 166 | 167 | } 168 | // Accept code on command line. 169 | fmt.Println(url) 170 | fmt.Print("Enter code: ") 171 | scanner := bufio.NewScanner(os.Stdin) 172 | code := "" 173 | for scanner.Scan() { 174 | line := scanner.Text() 175 | fmt.Println(line) 176 | code = line 177 | break 178 | } 179 | 180 | // This code caches the authorization code on the local 181 | // filesystem, if necessary, as long as the TokenCache 182 | // attribute in the config is set. 183 | token, err = config.Exchange(ctx, code) 184 | if err != nil { 185 | return nil, err 186 | } 187 | data, err := json.Marshal(token) 188 | ioutil.WriteFile(*cacheFile, data, 0644) 189 | } 190 | 191 | return oauth2.NewClient(ctx, oauth2.StaticTokenSource(token)), nil 192 | } 193 | 194 | func handleError(err error, message string) { 195 | if message == "" { 196 | message = "Error making API call" 197 | } 198 | if err != nil { 199 | log.Fatalf(message+": %v", err.Error()) 200 | } 201 | } 202 | 203 | func addPropertyToResource(ref map[string]interface{}, keys []string, value string, count int) map[string]interface{} { 204 | for k := count; k < (len(keys) - 1); k++ { 205 | switch val := ref[keys[k]].(type) { 206 | case map[string]interface{}: 207 | ref[keys[k]] = addPropertyToResource(val, keys, value, (k + 1)) 208 | case nil: 209 | next := make(map[string]interface{}) 210 | ref[keys[k]] = addPropertyToResource(next, keys, value, (k + 1)) 211 | } 212 | } 213 | // Only include properties that have values. 214 | if count == len(keys)-1 && value != "" { 215 | valueKey := keys[len(keys)-1] 216 | if valueKey[len(valueKey)-2:] == "[]" { 217 | ref[valueKey[0:len(valueKey)-2]] = strings.Split(value, ",") 218 | } else if len(valueKey) > 4 && valueKey[len(valueKey)-4:] == "|int" { 219 | ref[valueKey[0:len(valueKey)-4]], _ = strconv.Atoi(value) 220 | } else if value == "true" { 221 | ref[valueKey] = true 222 | } else if value == "false" { 223 | ref[valueKey] = false 224 | } else { 225 | ref[valueKey] = value 226 | } 227 | } 228 | return ref 229 | } 230 | 231 | func createResource(properties map[string]string) string { 232 | resource := make(map[string]interface{}) 233 | for key, value := range properties { 234 | keys := strings.Split(key, ".") 235 | ref := addPropertyToResource(resource, keys, value, 0) 236 | resource = ref 237 | } 238 | propJson, err := json.Marshal(resource) 239 | if err != nil { 240 | log.Fatal("cannot encode to JSON ", err) 241 | } 242 | return string(propJson) 243 | } 244 | func printVideosUpdateResults(response *youtube.Video) { 245 | // Handle response here 246 | } 247 | 248 | func videosUpdate(service *youtube.Service, part string, res string) { 249 | resource := &youtube.Video{} 250 | if err := json.NewDecoder(strings.NewReader(res)).Decode(&resource); err != nil { 251 | log.Fatal(err) 252 | } 253 | call := service.Videos.Update(part, resource) 254 | response, err := call.Do() 255 | handleError(err, "") 256 | printVideosUpdateResults(response) 257 | } 258 | 259 | func main() { 260 | flag.Parse() 261 | 262 | client, err := buildOAuthHTTPClient(youtube.YoutubeForceSslScope) 263 | handleError(err, "Error building OAuth client") 264 | 265 | service, err := youtube.New(client) 266 | handleError(err, "Error creating YouTube client") 267 | 268 | properties := (map[string]string{"id": "", 269 | "snippet.categoryId": "", 270 | "snippet.defaultLanguage": "", 271 | "snippet.description": "", 272 | "snippet.tags[]": "", 273 | "snippet.title": "", 274 | "status.privacyStatus": "", 275 | }) 276 | res := createResource(properties) 277 | 278 | // Note: service variable must already be defined. 279 | videosUpdate(service, "snippet,status", res) 280 | 281 | } 282 | --------------------------------------------------------------------------------