Rust Beginner's Guide
| Author | Date |
|---|---|
| Joe Mooney | 2022-07-23 14:40:41 |
A beginner's guide to programming in Rust written by a beginner.
A beginner's guide to writing a book like this using mdbook.
Instructions on Writing your own Book or Edit this Book
Installation Dependencies
cargo install mdbook- If you get an error about cannot find crti.o:
sudo apt install libc-dev npm i -g live-serverin combination withmdbook watch --openthis will reload the page anytime there are updates.
There is also another crate mdbook-plus I wrote that is used when building the book which has the extra features to simplify editing the book with custom markdown capabilities.
Viewing the Book while Editing or the Published Version
- View the book in github here: https://joemooney.github.io/rust-notes/
- View the book locally: file:///home/jpm/rust/rust-notes/book/index.html
- View locally while making updates:
./watch.shThis will open the book in the browser and listen for changes to the book in the filesystem and regenerate the book.
Goals
- document lessons learned in Rust from a beginner's perspective.
- create a template and supporting scripts for writing similarly layed-out books on any subject.
Notes on Markdown
- Code in single backticks looks like
enclosed in single backticks - Code in triple backticks looks like
enclosed in triple backticks - When you want a newline then end the line in two spaces
- ``` block of code ``` produces
block of code
- A literal ` is produced with a using \`
Layout of Book repo
- The md directory contains the book
- A README.md at the root level which is synced with a copy in the md folder.
⚠️ Please edit the md/README.md file and not the one in the top level in order for the changes to be automatically detected. Otherwise, the change will only be synced during publish. - md/SUMMARY.md all pages must be recorded in this file - this is table of contents in the left bar
- src/*.rs is where all the example rust code is stored, the md files can include code blocks from the same code files via text like
{{{{#include ../src/misc/anchors.rs:anchor4} }}}
will generate
// this is text // take from a anchor.rswhere the code displayed came from
Authoring the Book
- Build/View loop:
watch.shwhich callsmdbook watch --open- Opens the local book in a browser
- Then you edit pages in md subdirectory
- This will trigger a rebuild upon any change of the book
- See the updates on browser with web page refresh.
watch.shuseslive-serverwhich will do a hot reload of the browser.
- Just Build/View the book locally:
mdbook build - Note: README.md in the root directory of the repo is a copy of md/README.md and you should edit the version in the
mddirectory.publish.shwill copy the newer of either of these to the other, but the watch will not trigger a rebuild unless you edit themddirectory version. - Publish to github:
./publish.shshell script will generate and will publish changes to the book to github.
Dependencies
mdbook-plus
Clone, build, and install this repo https://github.com/joemooney/mdbook-plus
This is a mdbook preprocessor that I wrote. I use it for colored text and my quiz feature since I could not find an easier way to do these things.
If you get a WARN 2021-12-04 14:45:30 [WARN] (mdbook::preprocess::cmd): The command wasn't found, is the "plus" preprocessor installed? when you run mdbook build --open it means you have not installed my mdbook helper mdbook-plus
To install clone the repo and then cargo build and cargo install --path .
Install/Bootstrap (Cloning this Book and Editing)
- Clone the GitHub repo for this book: https://github.com/joemooney/rust-notes/
Once opened, the book contains the instructions for creating a new book, and editing, publishing etc.
The book contents (markdown) is contained in the md directory.
Any time you change contents in the md directory then the ./watch.sh will detect the change and regenerate the book locally and hot reload the browser.
Publishing
To publish changes to github run the publish.sh script. Use the -f force option if it reports there are no changes but you really know that there are and want to publish anyhow. Publishing will require access to github. It is best to install the gh command line tool from github https://github.com/cli/cli and then gh auth login and create a SSH login for your account.
Once published then other people will be able to see your book in <your_name>.github.io/<your_repo> in a few minutes.
Checking your Installation
- Check the following Q&A feature works.
- Check the following interactive code runs (it uses Rust Playgound)
Q&A and Interactive Code Example Feature
When viewing the book, make sure this feature is working so that you can use the interactive editor to run Rust code while reading the book. Not only can you run code, but you can edit and make changes (if the block is marked editable like the following Rust code block)
Here is an example of embedding a question and answer code block into the book which the reader can edit and run using rust playground: If you see a '?Q' and '?A' then you have not installed mdbook-plus (see above)
Q: (<-- click on the arrow to reveal the answer) What will happen when you run this code?
fn main(){
let _x:Option<u32> = None.unwrap();
}
A:
You program panics, because you cannot unwrap a None.
It is best not to use `unwrap` unless you know there is no possibility of failure.
fn main(){
let _x:Option<u32> = None.unwrap();
}
Editing the Book
Most of the work spent on the book is writing the scripts and tools for generating the layout of book as opposed to the contents.
This book is generated using a gitbook-like clone called mdbook. The book is stored in github and so the book serves the additional purpose of documenting how to create a similar book.
I have spent time on adding logic to make editing the book and producing content like question and answer blocks easier to write in pseudo-markdown.
Included are commands and instructions for creating a new empty book and how to go about publishing the book on github.
In addition to mdbook there some add-ons to mdbook stored in this repo. There is a preprocessor that makes it easy to generate an interactive quiz.
TODO: find out how to trigger hot page reload in the browser when I am editing.
Create a New Book
Clone this repo and empty out the md directory leaving just the README.md and SUMMARY.md. Also you can remove the src directory.
TODO: write a helper script to do this.
Dependencies for building the Book
See setup.sh where I have cobbled some of the installation commands together into a script.
Install rust
https://www.rust-lang.org/tools/install
Install graphvix
sudo apt install default-jre
sudo apt install graphviz
NPM Modules (optional)
npm i -g live-server
Install mdbook
cargo install mdbook
Install mdbook-plus (mine)
gh clone mdbook-pluscd mdbook-plus./install.sh
Install plantuml
https://github.com/plantuml/plantuml/releases/tag/v1.2021.16 https://plantuml.com/download
wget https://github.com/plantuml/plantuml/releases/download/v1.2021.16/plantuml-1.2021.16.jarsudo apt install libssl-devcargo install mdbook-plantuml
Troubleshootking
- failed complaining about openssl library not found, fix: sudo apt install libssl-dev
mdbook buildcomplains about plantuml not installed, fix: maybe `mdbook-plantuml is not installedReadmeis empty; fixcp README.md md/README.md
Other Goodies
A place to note any package of interest to install on a new machine.
Install gh
github's official command line tool.
https://github.com/cli/cli/blob/trunk/docs/install_linux.md
Install vs code
https://code.visualstudio.com/docs/?dv=linux64_deb
Miscellaneous Packages/Crates
cargo install cargo-editcargo install mdcat- nice cat command for md files.sudo apt install jq- json query tool, it also pretty prints json piped to it
To get focus to follow mouse :
sudo apt install gnome-tweaks- then rungnome-tweaksand enableSecondary FocusunderWindows
npm
Install nvm as recommended by npm https://docs.npmjs.com/downloading-and-installing-node-js-and-npm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
Install npm/node via nvm
nvm install node
wrangler
cargo install wrangler
Introduction
Last Updated: 2022-07-23 14:40:41
This book contains notes from learning Rust and notes on how this book was put together.
Generally useful tools:
Here are some useful tools:
- rust
- cargo
- mdbook
- cargo
- GitHub
- GitHub Pages
- gt the gitlab command line tool
- git
- rsync
- VS Code
- Extensions:
- docs-markdown :star: ⬅️ Alt-M to see help
- Git Graph
- GitLens
- Rust
- crates - thumbs up if cargo.toml dependencies are up-to-date
- TabNine
- Vim
- Extensions:
- Firefox
- Extensions
- vimium ⬅️ ? for help when in browser, navigate in browser like Vim
- Extensions
- Linux
- terminator
Editing the Book
- Having created the book
- Run
./watch.sh - Keep the
SUMMARY.mdfile open in order to add new pages- Ctrl + Click will open the editor for a page link
- If you edit any file under the
mdfolder you should see the book regenerated in the terminal where thewatch.shis running.- The message
The mdbook-plus preprocessor not running ...can be ignored.
- The message
| Markdown |
|---|
plantuml @startuml autonumber "<b>[0]" skinparam responseMessageBelowArrow true title Example Sequence Diagram |
| Generated Image |
Supporting Diagrams
Plantuml is used to generate supporting diagrams. Using mdbook-plantuml postprocessor we can use markdown to represent the diagrams and a corresponding image file will be generated upon book generation.
Book Layout
Create a Book
How to create a markdown book using mdbook crate.
This is the same crate used to create the official
Rust Programming Book.
It is also the crate used to this book.
mdbook allows you to add preprocessors so that you can have your own custom markdown extensions (like I do with mdbook-plus).
- cargo install mdbook
- mkdir mybook
- cd mybook
- mdbook init
Another example preprocessor is mdbook-toc
This book's git repo includes my mdbook-plus preprocessor that adds question/answer and other capabilities.
For a Table of Contents, I use mdbook-toc.
Preprocessors are powerful, they have access to the entire parsed token tree. This allows for finer grain control over changes. ⚠️ mdbook-plus is very rudimentary.
To publish this book there is a simple shell script publish.sh the outline of which is:
url=$(git config --get remote.origin.url | sed 's,git@github.com:,,;s,/,.github.io/,;s,^,https://,;s,.git$,/,')
lang=shell
mdbook build && \
postprocess && \
rsync -avx --delete --info=progress2 ./book/ ./docs/ &&\
git status && \
echo 'git commit -am' && \
echo -n "Comment: " && read comment && \
git add . && \
git commit -am "$comment" && \
git push && \
echo "Published: $url
There are some more guards such as checking for no git untracked files and no other git uncommitted updates.
As I am editing the book, I have watch.sh running which opens a browser and regenerates the book every time it changes.
mdbook-mermaid Preprocessor
Hmm, I am not using this any longer?
Looks like I switched to use plantuml (not sure why)
Upon cargo install mdbook-mermaid in book.toml add:
[preprocessor.mermaid]
renderer = ["html", "epub"]
Then we go: stateDiagram state "Also Cool" as s1 s1: foo/bar [] --> Cool Cool --> s1 s1 --> Coolest Coolest --> []
Custom mdbook-plus preprocessor for mdbook
mdbook-plus
The mdbook-plus is a rudimentary preprocessor for mdbook which does simple search and replace without tokenizing the input files. It takes as stdin markdown text with custom markdown and converts these substrings to HTML.
- Colorized Text
- Q&A Blocks
Here we take a look at the markdown you write looks like before and after going through the preprocessor - the markdown output by the preprocessor is fed into by mdbook to generate the final html for the book (in the book subdirectory) and see how it is rendered.
Note that the custom markers are replaced with html which the mdbook markdown to html leaves as is.
Also note you can use '[#open_bracket]' instead of brackets etc. from being processed as markdown for the special cases - though you probably will never need to worry about that.
The preprocessing happens as part of mdbook build.
publish.sh will run this and publish the generated book to github.
Before Colorized Preprocessor (markdown who you write)
- text color {red}red font{/red} {blue}blue{/blue} {green}green{/green} {yellow}yellow{/yellow} {gray}gray{/gray}<br>
- subscripted text {small}subscript font{/small}
After Colorized Preprocessor
- text color <span style='color:red'>red font</span> <span style='color:lightblue'>blue</span> <span style='color:green'>green</span> <span style='color:yellow'>yellow</span> <span style='color:gray'>gray</span>
- subscripted text <sub>subscript font</sub>
Final Rendering by mdbook
- text color red font blue green yellow gray
- subscripted text subscript font
Question/Answer Blocks
Support collapsable question/answer blocks where the answer is hidden until you click the dropdown arrow. For this we substitute into the html details tag.
Before Q&A Preprocessor (markdown who you write)
?Q Example question goes here until we get to the answer
?A You answer goes here until we get to the end
but it is hidden so that the reader has a chance
to think before they reveal the answer
?E
After Q&A Preprocessor (html after preprocessing)
Q: Example question goes here until we get to the answer ?
A: You answer goes here until we get to the end but it is hidden so that the reader has a chance to think before they reveal the answer
Final Q&A Rendering in Browser
Q: Example question goes here until we get to the answer ?
A: You answer goes here until we get to the end but it is hidden so that the reader has a chance to think before they reveal the answerInstall
cargo install --path . --bin mdbook-plus
Escaping
- The preprocessing is just dumb string replacement. So, if you want literal {red} in your output then you can put a backslash before ending brackets so the text substitution does not match (i.e. {red\} which should show as {red})
- You can also put [#open_bracket]red[#close_bracket] and likewise for '[#dash]' for '-', '[#question_mark]' for '?'.
Install mdbook-plus
cargo install --path . --bin mdbook-plus
Escaping Braces
- The preprocessing is just dumb string replacement. So, if you want literal {red} in your output then you can put a backslash before ending brackets so the text substitution does not match (i.e. {red\} which should show as {red})
- You can also put [#open_bracket]red[#close_bracket] and likewise for '[#dash]' for '-', '[#question_mark]' for '?'.
Markdown with escaped characters
[#question_mark\]Q Example question goes
here until we get to the answer<br>
[#question_mark\]A You answer goes here until we get to the end<br>
but it is hidden so that the reader has a chance<br>
to think before they reveal the answer<br>
[#question_mark\]E
- text color [#open_bracket\]red[#close_bracket\]red font[#open_bracket\]/red[#close_bracket\] [#open_bracket\]blue[#close_bracket\]blue[#open_bracket\]/blue[#close_bracket\] [#open_bracket\]green[#close_bracket\]green[#open_bracket\]/green[#close_bracket\] [#open_bracket\]yellow[#close_bracket\]yellow[#open_bracket\]/yellow[#close_bracket\] [#open_bracket\]gray[#close_bracket\]gray[#open_bracket\]/gray[#close_bracket\]<br>
- subscripted text [#open_bracket\]small[#close_bracket\]subscript font[#open_bracket\]small[#close_bracket\]
Limitations
- Since these special markers in the input markdown files are not legitimate for normal markdown, intellisense in VS Code (for example) may flag lines with warnings.
- Note:
code in backticksdoes not render correctly inside color blocks.
Cargo
Cargo is the Rust build and pacakage management system.
A package is called a Crate (like an npm module or .net nuget).
-
To create a new project crate:
cargo new mycratecd mycrate- git init .
- hub new (hub is deprecated, github now has a
ghcommand)
-
To upgrade all your your existing crates you have installed run
cargo install-update -a
Say you have an existing directory, an empty git repo.
To turn this into a Rust project you run:
cargo init . --bin
If you then execute cargo run it will compile and run it, printing out Hello World!
Cargo Add-ons
cargo install cargo-edit
this allows you to cargo add/rm <crate> and cargo upgrade --dry-run
Crates
Crates are libraries or binaries that are typically stored in crates.io An alterative index that for me is easier to find crates of interest is: lib.rs
Below are some crates that I have found useful with associated notes.
Coding
There are many cool crate executables and the running cargo install to install them is a breeze (installation will take some time depending on the number of dependencies).
Subprocess/Shell
MdBook related
- cargo install mdbook-mermaid
- cool flowcharts and other graphs for your book
Executable Crates
- cargo install cargo-upadte
- cargo-update
- then
cargo install-update --listwill list all the installed executables to check if they are up to date. cargo install-update --allwill update all installed executables to check if they are up to date.
- ripgrep
- better, faster grep
Executable Crates
Executable or Binary Crates are binaries that are typically stored in crates.io
and installed via cargo install <crate>.
Below are some executeble crates that I have found useful with associated notes.
- cargo install cargo-upadte
- cargo-update
- then
cargo install-update --listwill list all the installed executables to check if they are up to date. cargo install-update --allwill update all installed executables to check if they are up to date.
- ripgrep
- better, faster grep
ripgrep
AnyMap Crate
Map a key to a value where the key is a type and the value is an instance of that type.
- Store configuration data
- Singleton
Create lots of simple types, for example
extern crate anymap; use std::net::Ipv4Addr; use anymap::AnyMap; #[derive(Debug)] enum HostAddress { DomainName(String), Ip(Ipv4Addr), } #[derive(Debug)] struct Port(u32); #[derive(Debug)] struct ConnectionLimit(u32); let mut config = AnyMap::new(); config.insert(HostAddress::DomainName("siciarz.net".to_string())); config.insert(Port(666)); config.insert(ConnectionLimit(32)); println!("{:?}", config.get::<HostAddress>()); println!("{:?}", config.get::<Port>()); assert!(config.get::<String>().is_none()); assert!(config.get::<u32>().is_none());
Here we have types e.g. Port instead of having a port being a member of some struct we can have an AnyMap and have Port as one of the keys.
notify
How do I recursively watch file changes in Rust?
This example watches a directory for changes and upon a change an event is received
use notify::{Watcher, RecursiveMode, watcher}; use std::sync::mpsc::channel; use std::time::Duration; fn main() { // Create a channel to receive the events. let (sender, receiver) = channel(); // Create a watcher object, delivering debounced events. // The notification back-end is selected based on the platform. // The Duration is how long after the event that you will // receive the notification. If this is too short then you // may end up taking an action before the event is complete. // For example, if a file is written to you may try something // before it is finished writing. let mut watcher = watcher(sender, Duration::from_secs(2)).unwrap(); // Add a path to be watched. All files and directories at that path and // below will be monitored for changes. watcher.watch("/path/to/watch", RecursiveMode::Recursive).unwrap(); loop { match receiver.recv() { Ok(event) => println!("{:?}", event), Err(e) => println!("watch error: {:?}", e), } } }
Storing on github
Create a repo locally:
mdbook rust-book⬅️ this will create the directory with a bare bones bookcd rust-bookgit init
Then I installed hub command to simplify working with github:
sudo snap install [hub](https://hub.github.com/) --classichub create> [!TIP]This will create the repo on GitHub, you will be prompted from user/password first time thru.
git remote show origingit push --set-upstream origin master
After creating some content for the book and pushing the changes to GitHub,
I then ran mdbook build which generated a book directory.
mdbook build --open- Once you are happy with the changes you can publish.
mv book docsgit add docs⬅️ this must be the name. It is what GitHub Pages requires.git commit -m 'adding my book for the first time'- On GitHub, under Settings for the repo, under GitHub Pages section, I selected Source master branch/docs
git push- The link to your site will be shown as https://<your_id>.github.io/<your_repo>/
- After you make changes to the book you can sync the book and docs directories.
rsync -avx --delete --info=progress2 ./book/ ./docs/git push
Sample Code
Here we go through code samples from various sources on the web.
Hello World
fn main() { print!("Hello World!"); }
fn main() { let number = 5; print!("{} {number}", number); }
//TODO: how dow import in an mdbook block of code? use itertools::Itertools; fn main() { let numbers = vec![1,2,3,4,5,6,7,8,9]; // To use itertools // numbers.foreach(|i| print!("{}", i)); for i in numbers { print!("{i}"); } }
Rust Cookbook
The code is all taken from the Rust Cookbook, the links are back to the Rust Cookbook. The snippets here are just a condensed version without explanation. Read the Rust Cookbook to understand.
Random Numbers
extern crate rand; use rand::Rng; fn main() { let mut rng = rand::thread_rng(); let n1: u8 = rng.gen(); let n2: u16 = rng.gen(); println!("Random u8: {}", n1); println!("Random u16: {}", n2); println!("Random u32: {}", rng.gen::<u32>()); println!("Random i32: {}", rng.gen::<i32>()); println!("Random float: {}", rng.gen::<f64>()); println!("Integer: {}", rng.gen_range(0..10)); println!("Float: {}", rng.gen_range(0.0..10.0)); }
Sorting
fn main() { let mut vec = vec![1, 5, 10, 2, 15]; vec.sort(); assert_eq!(vec, vec![1, 2, 5, 10, 15]); }
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] struct Person { name: String, age: u32 } impl Person { pub fn new(name: String, age: u32) -> Self { Person { name, age } } } fn main() { let mut people = vec![ Person::new("Zoe".to_string(), 25), Person::new("Al".to_string(), 60), Person::new("John".to_string(), 1), ]; // Sort people by derived natural order (Name and age) people.sort(); assert_eq!( people, vec![ Person::new("Al".to_string(), 60), Person::new("John".to_string(), 1), Person::new("Zoe".to_string(), 25), ]); // Sort people by age people.sort_by(|a, b| b.age.cmp(&a.age)); assert_eq!( people, vec![ Person::new("Al".to_string(), 60), Person::new("Zoe".to_string(), 25), Person::new("John".to_string(), 1), ]); }
Command Line Argument Parsing
use clap::{Arg, App}; fn main() { let matches = App::new("My Test Program") .version("0.1.0") .author("Hackerman Jones <hckrmnjones@hack.gov>") .about("Teaches argument parsing") .arg(Arg::with_name("file") .short("f") .long("file") .takes_value(true) .help("A cool file")) .arg(Arg::with_name("num") .short("n") .long("number") .takes_value(true) .help("Five less than your favorite number")) .get_matches(); let myfile = matches.value_of("file").unwrap_or("input.txt"); println!("The file passed is: {}", myfile); let num_str = matches.value_of("num"); match num_str { None => println!("No idea what your favorite number is."), Some(s) => { match s.parse::<i32>() { Ok(n) => println!("Your favorite number must be {}.", n + 5), Err(_) => println!("That's not a number! {}", s), } } } }
Rosetta Code
Rosetta Code is a list of problems/solutions written is basically every programming language under the sun.
100 Doors
This my first foray into writing a simple algorithm in Rust.
Lessons Learned
- Don't use
vec!for a fixed size list (array)let mut doors = vec![false; 100];let mut doors = [false; 100]; - Translate a bool into a string - nice!: a. println!("{}", if d {"open"} else {"closed"});
for (i, &is_open) in door_open.iter().enumerate()a. Maybe this is faster/more idiomatic than my approach - but less readable? Based on comments perhaps this approach is faster due to printing being a bottleneck. I don't understand.- Rust is (at least) two languages bundled into one: a. A procedural and a declarative (functional) language b. The procedural language is easier to understand for this problem. c. The procedural language requires you become familiar with a limited set of verbs: let, for, if, while etc. d. The declarative language requires you become familiar with many more verbs: filter, iterate, enumerate, map, last, any, find, skip, nth, take, take_while, etc. e. The magic of Rust is that you can get similar (sometimes better) performance in Declarative versus Procedural. f. To be a good Rust programmer, you should become familiar with both styles of programming. In the longer term, it is probably better to prefer the Declarative style. g. In many cases, parallelization can be trivially achieved in the Declarative style.
My Soution
First roseatta code problem attempt in Rust (not looking at solution)...
#![allow(unused)] fn main() { let mut doors = vec![false; 100]; for i in 0..100 { let mut d = i; while d < 100 { doors[d] = !doors[d]; d += i + 1; } } for d in 0..100 { println!(" {}/{}", d + 1, doors[d]); } }
Official Solution
Procedural
#![allow(unused)] fn main() { let mut door_open = [false; 100]; for pass in 1..101 { let mut door = pass; while door <= 100 { door_open[door - 1] = !door_open[door - 1]; door += pass; } } for (i, &is_open) in door_open.iter().enumerate() { println!("Door {} is {}.", i + 1, if is_open {"open"} else {"closed"}); } }
- 100 bools initialized to false
- for 100 passes
- walk until past 100 doors
- start at door# = pass#
- toggle state of door
- skip forward pass# number of doors
- walk until past 100 doors
Declarative
#![allow(unused)] fn main() { let doors = vec![false; 100] .iter_mut() .enumerate() .map(|(door, door_state)| (1..100).into_iter().filter(|pass| door % pass == 0) .map(|_| { *door_state = !*door_state; *door_state }) .last() .unwrap() ) .collect::<Vec<_>>(); println!("{:?}", doors); }
- 100 bools initialized to false
- mutable iterator
- enumerate to give (index, value) (i.e door, door_state)
- iterate 100 times for each (door, door_state)
- skip unless this pass includes this door state
- (door number / pass#) divides evenly
- toggle the door state (boolean)
- this produces a boolean list for this door
- (100 long for door #1; 1 long for door #100)
- skip unless this pass includes this door state
- take the last entry in this list
- this is the final state for the door
- because lists can be empty and there may not be a last element, so last() needs to return an Option<T> not a T.
- unwrap() the Option<T> into a T
- note: you are guaranteed to have at least one element becuase you visited every door, so the unwrap cannot panic.
- iterate 100 times for each (door, door_state)
- collect the results for all doors
-
100 prisoners are individually numbered 1 to 100
-
A room having a cupboard of 100 opaque drawers numbered 1 to 100, that cannot be seen from outside.
-
Cards numbered 1 to 100 are placed randomly, one to a drawer, and the drawers all closed; at the start.
-
Prisoners start outside the room
- They can decide some strategy before any enter the room.
- Prisoners enter the room one by one, can open a drawer, inspect the card number in the drawer, then close the drawer.
- A prisoner can open no more than 50 drawers.
- A prisoner tries to find his own number.
- A prisoner finding his own number is then held apart from the others.
-
If all 100 prisoners find their own numbers then they will all be pardoned. If any don't then all sentences stand.
Pseudo Code
- create boxes vec containing numbers 1 thru 100
- These numbers are the prison numbers
- hint: this is a collected range
- Prisoner guess: given the boxes and a prisoner number
- create guesses vec containing numbers 1 thru 100
- shuffle guesses
- for first 50 guesses
- return if any box corresponding to the guess contains a number matching prisoner number
- Perform trial:
- shuffle the boxes
- for all prisoners numbers 0 thru 100
- Prisoner guess
- Peform 1000 trials
- filter successes
- Perform trial
- count successes
- filter successes
Solution
#![allow(unused)] fn main() { let boxes = (1u8..101u8).collect(); fn prisoner_guess(boxes: mut &[u8], prisoner_number) -> bool { let guesses = { boxes.shuffle() } } }
Official Solution
extern crate rand; use rand::prelude::*; // Do a full run of checking boxes in a random order for a single prisoner fn check_random_boxes(prisoner: u8, boxes: &[u8]) -> bool { let checks = { let mut b: Vec<u8> = (1u8..=100u8).collect(); b.shuffle(&mut rand::thread_rng()); b }; checks.into_iter().take(50).any(|check| boxes[check as usize - 1] == prisoner) } // Do a full run of checking boxes in the optimized order for a single prisoner fn check_ordered_boxes(prisoner: u8, boxes: &[u8]) -> bool { let mut next_check = prisoner; (0..50).any(|_| { next_check = boxes[next_check as usize - 1]; next_check == prisoner }) } fn main() { let mut boxes: Vec<u8> = (1u8..=100u8).collect(); let trials = 100000; let ordered_successes = (0..trials).filter(|_| { boxes.shuffle(&mut rand::thread_rng()); (1u8..=100u8).all(|prisoner| check_ordered_boxes(prisoner, &boxes)) }).count(); let random_successes = (0..trials).filter(|_| { boxes.shuffle(&mut rand::thread_rng()); (1u8..=100u8).all(|prisoner| check_random_boxes(prisoner, &boxes)) }).count(); println!("{} / {} ({:.02}%) successes in ordered", ordered_successes, trials, ordered_successes as f64 * 100.0 / trials as f64); println!("{} / {} ({:.02}%) successes in random", random_successes, trials, random_successes as f64 * 100.0 / trials as f64); }
15 Puzzle Game
Pseudo Code
Define enums for Action, Direction, and Cell types Define a Board type Define an EMPTY constant Define a struct for the P15 game- Constructor
- fn is_valid(mut board: Board) -> bool {
- find empty cell: return the location of the empty cell
- get_moves return a hash map of direction: cell
- play given a Direction
- fn is_complete(&self) -> bool {
- Implement Display trait for P15
- write fmt function fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Implement user interface for function to ask user to Move a cell up/down/left/right Or Quit Use get_move to see what Cell can move in what direction Perform the move
Solution
extern crate rand; use std::collections::HashMap; use std::fmt; use rand::Rng; use rand::seq::SliceRandom; #[derive(Copy, Clone, PartialEq, Debug)] enum Cell { Card(usize), Empty, } #[derive(Eq, PartialEq, Hash, Debug)] enum Direction { Up, Down, Left, Right, } enum Action { Move(Direction), Quit, } type Board = [Cell; 16]; const EMPTY: Board = [Cell::Empty; 16]; struct P15 { board: Board, } impl P15 { fn new() -> Self { let mut board = EMPTY; for (i, cell) in board.iter_mut().enumerate().skip(1) { *cell = Cell::Card(i); } let mut rng = rand::thread_rng(); board.shuffle(&mut rng); if !Self::is_valid(board) { // random swap let i = rng.gen_range(0, 16); let mut j = rng.gen_range(0, 16); while j == i { j = rng.gen_range(0, 16); } board.swap(i, j); } Self { board } } fn is_valid(mut board: Board) -> bool { // TODO: optimize let mut permutations = 0; let pos = board.iter().position(|&cell| cell == Cell::Empty).unwrap(); if pos != 15 { board.swap(pos, 15); permutations += 1; } for i in 1..16 { let pos = board .iter() .position(|&cell| match cell { Cell::Card(value) if value == i => true, _ => false, }) .unwrap(); if pos + 1 != i { board.swap(pos, i - 1); permutations += 1; } } permutations % 2 == 0 } fn get_empty_position(&self) -> usize { self.board.iter().position(|&c| c == Cell::Empty).unwrap() } fn get_moves(&self) -> HashMap<Direction, Cell> { let mut moves = HashMap::new(); let i = self.get_empty_position(); if i > 3 { moves.insert(Direction::Up, self.board[i - 4]); } if i % 4 != 0 { moves.insert(Direction::Left, self.board[i - 1]); } if i < 12 { moves.insert(Direction::Down, self.board[i + 4]); } if i % 4 != 3 { moves.insert(Direction::Right, self.board[i + 1]); } moves } fn play(&mut self, direction: &Direction) { let i = self.get_empty_position(); // This is safe because `ask_action` only returns legal moves match *direction { Direction::Up => self.board.swap(i, i - 4), Direction::Left => self.board.swap(i, i - 1), Direction::Right => self.board.swap(i, i + 1), Direction::Down => self.board.swap(i, i + 4), }; } fn is_complete(&self) -> bool { self.board.iter().enumerate().all(|(i, &cell)| match cell { Cell::Card(value) => value == i + 1, Cell::Empty => i == 15, }) } } impl fmt::Display for P15 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { r#try!(write!(f, "+----+----+----+----+\n")); for (i, &cell) in self.board.iter().enumerate() { match cell { Cell::Card(value) => r#try!(write!(f, "| {:2} ", value)), Cell::Empty => r#try!(write!(f, "| ")), } if i % 4 == 3 { r#try!(write!(f, "|\n")); r#try!(write!(f, "+----+----+----+----+\n")); } } Ok(()) } } fn main() { let mut p15 = P15::new(); for turns in 1.. { println!("{}", p15); match ask_action(&p15.get_moves()) { Action::Move(direction) => { p15.play(&direction); } Action::Quit => { println!("Bye !"); break; } } if p15.is_complete() { println!("Well done ! You won in {} turns", turns); break; } } } fn ask_action(moves: &HashMap<Direction, Cell>) -> Action { use std::io::{self, Write}; use Action::*; use Direction::*; println!("Possible moves:"); if let Some(&Cell::Card(value)) = moves.get(&Up) { println!("\tU) {}", value); } if let Some(&Cell::Card(value)) = moves.get(&Left) { println!("\tL) {}", value); } if let Some(&Cell::Card(value)) = moves.get(&Right) { println!("\tR) {}", value); } if let Some(&Cell::Card(value)) = moves.get(&Down) { println!("\tD) {}", value); } println!("\tQ) Quit"); print!("Choose your move : "); io::stdout().flush().unwrap(); let mut action = String::new(); io::stdin().read_line(&mut action).expect("read error"); match action.to_uppercase().trim() { "U" if moves.contains_key(&Up) => Move(Up), "L" if moves.contains_key(&Left) => Move(Left), "R" if moves.contains_key(&Right) => Move(Right), "D" if moves.contains_key(&Down) => Move(Down), "Q" => Quit, _ => { println!("Unknown action: {}", action); ask_action(moves) } } }
15 puzzle solver
Linux Environment
Tools and utilities for Linux.
Non-Rust Utilities
Terminator
Language Features
Dropping/Destructors
struct PrintOnDrop(&'static str, u32); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("{}", self.0); } } fn main() { let y = PrintOnDrop("dropping y will happening at exit of main", 1); println!("<<<<<<<<<before block>>>>>>>>>>>>"); { let mut x = PrintOnDrop("x=2 shadowed on next line, but not dropped", 2); let z = PrintOnDrop("z=2 manually dropped", 2); println!("z={}", z.1); drop(z); // we can manually drop let x = PrintOnDrop("x=3 drops when scope ends", 3); println!("x={}", x.1) // x=3 dropped // x=2 dropped (reverse order of creation) } println!("<<<<<<<<<<<after block>>>>>>>>>>>>"); // y=1 dropped }
Memory
References
https://stackoverflow.com/questions/30938499/why-is-the-sized-bound-necessary-in-this-trait
Passing Arguments and Returning Values
- Functions must return a known size of memory.
- Functions parameters must be of a known size of memory.
- Local variables must be of a known size.
- Functions must return a concrete sized type - unlike other languages.
- A concrete type may be sized or unsized.
- Traits are not concrete - their size is unknown.
- You cannot return Traits.
- Place Traits in a Box.
- A Box is a reference to heap memory.
- A reference has a known size - it is a pointer.
- Rust prefers code to be explicit if memory is heap or stack.
dynindicates that memory is in the heap.- For a generic function we need to ensure that the arguments are sized.
- Rust defaults all generic type parameters to be
Sized. Sofn generic_fn<T>(x: T) -> T { ... }is the equivalent offn generic_fn<T: Sized>(x: T) -> T { ... }. But you may not want that. Sofn generic_fn<T: ?Sized>(x: &T) -> u32 { ... }allows you to call generic_fn("abc") whereT == strwhich is an unsized typestrbut the argument is&Twhich is sized so all is good. traithas an implicit?Sizedbound onSelf.traits can be implemented for sized and unsized types. For example, any trait which only contains methods which only take and return Self by reference can be implemented for unsized types.- If you want to return
Selfby value or acceptSelfas an argument by value, you need to either bind the trait withSizedor bind the function withSized:trait A: Sized { ... }or
#![allow(unused)] fn main() { trait WithConstructor { fn new_with_param(param: usize) -> Self; fn new() -> Self where Self: Sized, { Self::new_with_param(0) } } }
Macros
References
-
Rust macros are good since they generally:
- avoid unintended side-effects
- do not result in bizarre error messages
- are not simple text substitutions, they must be parseable
- are subject to much more rigorous analysis during compilation
- are an elegant way to add a lot of power to a program, with limited risk
- are well thought out and tightly integrated into the ecosystem
- have few detractors within the Rust community
- can only expand to what is expected at that position (e.g. expression or type definition)
- can never expand to an incomplete or invalid construct (e.g. unbalanced parenthesis). In C you could
#define Foo Bar(that is impossible in Rust. - can be recursive (up to 32 levels by default)
- are actually a big selling point of the language
Traits
See Also: Trait Objects See Also: Object Safety
Per https://doc.rust-lang.org/rust-by-example/trait.html, a trait is a collection of methods defined for an unknown type: Self. They can access other methods declared in the same trait.
Q: Is a trait is type?
A: YesCombine multiple traits to for a new trait
Trait Objects
See Also: Traits
A function that has an argument of &dyn T is a trait object.
Only object-safe traits are candidates - this eliminates Traits that have any functions with generic arguments or return types.
Why use trait objects?
- Trait objects allow you have collections of objects that implement the same trait but are of different types.
- Smaller binaries (polymorphic dynamic dispatch functions instead of monomorphic static dispatch generic functions)
- Apparently, the additional runtime overhead is small.
- Increasing your use of Traits is good practice.
Why not use trait objects?
- Trait objects result in slower binaries.
- Compiler cannot perform inlining optimizations, so if your function is small and frequently called this may not be preferable.
#![allow(unused)] fn main() { // https://stevedonovan.github.io/rustifications/2018/09/08/common-rust-traits.html use std::string::ToString; // Note the signatures are different but the implementations are the same // Using an Object Reference (not Trait Object), monomorphic - generic function fn to_string1<T: ToString> (item: &T) -> String { item.to_string() } // Using a Trait Object, polymorphic - dynamic dispatch fn to_string2(item: &dyn ToString) -> String { item.to_string() } // From the calling side there is no difference, but the code generated is different println!("{}", to_string1(&42)); // uses a one version of function to_string1 expanded for &u32 println!("{}", to_string1(&"hello")); // uses a different version of function to_string1 expanded for &str println!("{}", to_string2(&42)); // uses the only version of function to_string2 passing as a trait object &u32 println!("{}", to_string2(&"hello")); // uses the only version of function to_string2 passing as a trait object &str }
Another example of using a Vec of trait objects looks pretty slick.
#![allow(unused)] fn main() { // https://dev.to/magnusstrale/rust-trait-objects-in-a-vector-non-trivial-4co5 use std::f32::consts::PI; #[derive(PartialEq)] struct Circle { radius: u32, } struct Square { side: u32, } trait Shape: Any { fn box_eq(&self, other: &dyn Any) -> bool; fn as_any(&self) -> &dyn Any; fn area(&self) -> u32; } impl Shape for Square { // boilerplate, same for all impl of Shape fn as_any(&self) -> &dyn Any { self } // boilerplate, same for all impl of Shape fn box_eq(&self, other: &dyn Any) -> bool { other.downcast_ref::<Self>().map_or(false, |a| self == a) } fn area(&self) -> u32 { self.size * self.size } } impl Shape for Circle { // boilerplate, same for all impl of Shape fn as_any(&self) -> &dyn Any { self } // boilerplate, same for all impl of Shape fn box_eq(&self, other: &dyn Any) -> bool { other.downcast_ref::<Self>().map_or(false, |a| self == a) } fn area(&self) -> u32 { self.radius.pow(2) * PI } } impl PartialEq for Box<dyn Shape> { fn eq(&self, other: &Box<dyn Shape>) -> bool { self.box_eq(other.as_any()) } } fn do_stuff(objects: Vec<Box<dyn Shape>>) { let obj1 = &objects[0]; let obj2 = &objects[1]; if obj1 == obj2 { println!("Equal"); } } }
Here we see a Vec of a Trait type which can contain different types of concrete objects which can be differentiated.
If you want to pass a variable to a function which may be of different types at run time, then you can use a trait object. Alternatively, you can use a generic function will generate a different function for each type that calls the function but a trait object will use the same single function with dynamic dispatch to make any calls to methods of the trait object in the function.
A trait object is a value which we can pass as a parameter to a function and which has as its type a trait as opposed to a concrete type. This means that for all of the trait methods the sizes of the parameters and the return values must be known at compile time. The trait object has an unknown type at compile time because the underlying concrete type is unknown. Lots of types can implement a trait. Some of those types may have a size that changes during execution. For example, if a struct has a String or a Vec, the size of the struct will change as the values are changed. Such a type cannot be placed on the stack and as such it cannot be passed or returned from a function.
A trait is either object safe or not. If a trait is not object safe, it cannot be used to make a trait object. So, being object safe means that we can create a trait object for that Trait.
A trait object must be a reference (or pointer) since it must be a fat pointer containing a pointer to the object data and a pointer to the vtable of the concrete implementation of the functions of the trait.
You have to take the Trait Object by reference or pointer. Whether you use a reference or pointer depends on whether you want to transfer ownership or not.
This will not compile because a closure as an unknown size.
#![allow(unused)] fn main() { fn returns_closure() -> dyn Fn(i32) -> i32 { |x| x + 1 } }
This will compile: Vec
#![allow(unused)] fn main() { fn returns_closure() -> Box<dyn Fn(i32) -> i32> { Box::new(|x| x + 1) } }
Excercise
Write a function taking a Trait Object for Trait Foo.
Foo trait has a function foo with just &self as argument with no return - it would simply print "I am a Cat" where Cat is a struct that implements the Trait or "I am a Dog" for a Dog.
Object safety
Only an object-safe trait can be passed as a trait object parameter to a function.
All parameters to a function must have a known size a.k.a Sized.
Traits are ?Sized. They have an unknown size since the sizes may vary for different concrete types.
If a trait method returns Self, then from the perspective of the trait, the method is returning a type of varying size depending on the size of the concrete type.
A function cannot return something of unknown size.
It is fine to declare such a method for a trait because any concrete type that implements the Trait will need to be Sized or else compiler will know to complain.
But it is not okay to pass a reference to an object of that trait dyn &T as an argument to some other function since that other function may call the method returning Self.
Side Note: Rust does not look at the implementation to see if the method is actually being called - if a parameter is okay to be passed into a function then it should be okay to call any of its methods in the function logic.
) and that size may vary which is not allowed (we need to know how many bytes on the stack to allocate for the return value of a function before we call the function).
So, when a type
A trait is object safe if all the methods defined in the trait have the following properties: - The return type isn’t Self. - There are no generic type parameters.
For a trait to be object safe the underlying concrete type should be referred to by a &self reference and not by self value. A reference is a known size (it is the same size, the size of a pointer, regardless of type) but a value is unknown (it varies depending on the type).
trait T1 {
fn foo(self); // not object safe,
// size of object is unknowable at compile time.
}
trait T2 {
fn foo(&self); // object safe
}
Iterator
You implement an iterator for some custom collection type you define. Maybe you have a company struct which contains a vector
list of employees but the list may contain former employees
who should not be included in any iteration of the employees.
So company.employees may be an iterator.
Implementing the Iterator trait means implementing the next function.
You struct will need some index to point to the current location.
You
impl<'a> Iterator for Iter<'a> { type Item = u32; fn next(&mut self) -> Option<Self::Item> { // index or some pointer to current item while self.index + 1 < self.vec.len() { let prev = self.vec[self.index]; self.index += 1; if (*self.f)(prev) { return Some(self.vec[self.index]); } } None } }
Enums
Q: Can an Enum have methods?
A: Yesenum Pill { Blue, Red, } impl Pill { fn take(&self) { match self { Pill::Blue => println!("Welcome to the Matrix!"), Pill::Red => println!("You are awake, welcome to your nightmare!"), } } } fn main() { let c = Pill::Red; c.take(); }
Object Orientation
Inheritance
Favor composition over inheritance.
#[derive(Debug)] struct Person { name: String, age: u32, phone: u64, } #[derive(Debug)] struct Employee { person: Person, phone: u64, id: u32, } fn main() { let p = Person { name: "John".to_string(), age:33, phone:3334445555 }; let e = Employee {person:p, phone:1112223333, id:12345}; println!("{:?}", p); println!("{:?}", e); println!("{:?}, home phone:{}", e, e.person.phone); }
Comparisons with Other Language
Optional with default Parameters
There are pros and cons to supporting python equivalent of:
def foo(bar=20)
If we call foo(bar) and bar is None then the default does not
apply and
Although in this case there is no way not to provide the argument,
you can call foo(None). This has arguable benefit of allowing foo
to be called with an argument with value None and getting the
default, whereas in python you would not know whether the argument
was supplied and if it had None you would need to check for it.
#![allow(unused)] fn main() { fn foo(bar: Option<usize>) { let x = bar.unwrap_or(20); //Default value is 20 } }
Different signatures
C# lets you do this:
public static int Foo(int x = 0) => 20 * x;
public static int Foo() => 10;
Best Practices
- avoid using unwrap() or expect() since they panic. Use only when a panic is unavoidable.
Miscellaneous
Random useful information
rustup update to update your version of Rust.
cargo check to see if your crates are up-to-date.
Read a line of input
#![allow(unused)] fn main() { use std::io; let mut ans = String::new(); print!("Enter answer: "); io::stdin().read_line(&mut ans) .expect("Failed to read input"); }
cargo doc --open opens the documentation for all your dependencies.
Haskell Curry was an American mathematician and logician.
Default an error case with a match expression
let ans: u32 = match ans.trim().parse() {
Ok(num) => num,
Err(_) => 0,
}
Glossary
Rustonomicom
A book about writing unsafe code and the inner guts of Rust. |
block
A block is a set of statements enclosed in braces {...}. A block is an expression. Before the statements where may be inner attributes.
bound
When type is bounded by a capability it must provide that capability if required by any code that has access to variable of that type.
bound T: 'static
Note: T: 'static includes owned types.
A variable with this capability could be accessible for the lifetime of the program if that part of the code requires it to be so. That part of the code may not require it to be accessible for the lifetime of the program and if it owns the variable then it may drop it and the variable would obviously not have had a lifetime for the full duration of the program.
attributes
- Built-in attributes
- Macro attributes
- Derive macro helper attributes
- Tool attributes
inner attributes
Inner attributes #![allow(unused_variables)] apply to the item containing the attribute. Within a block this means the block may contain unused variables.
outer attributes
Outer attributes #[test] apply to the thing that follows the attribute.
Statement
A statement is a component of a block, which is in turn a component of an outer expression or function. Rust has two kinds of statement: declaration statements and expression statements.
expression statement
An expression statement is one that evaluates an expression and ignores its result.
expression
An expression may have two roles: it always produces a value, and it may have effects (otherwise known as "side effects"). An expression evaluates to a value, and has effects during evaluation. Many expressions contain sub-expressions (operands).
Declaration statements
A declaration statement is one that introduces one or more names into the enclosing statement block. The declared names may denote new variables or new items. The two kinds of declaration statements are item declarations and let statements.
monomorphic/polymorphic
A generic function is monomorphic. A function taking a trait object as an argument is polymorphic. There is a separate function generated by the compiler for each different type used to call a monomorphic function. There is just one polymorphic function generated by the compiler for a polymorphic function and static dispatch is employed at runtime to call concrete methods of the abstract trait object.
The monomorphic function is like a C++ templated function. The polymorphic function is like a C++ base class virtual method or Java interface. monomorphic is more efficient and can be inlined.
function pointer
Function pointer types, written using the fn keyword, refer to a function whose identity is not necessarily known at compile-time.
fn add_one(x: i32) -> i32 { x + 1 } fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 { f(arg) + f(arg) } fn main() { let answer = do_twice(add_one, 5); println!("The answer is: {}", answer); }
Unlike closures, fn is a type rather than a trait, so we specify fn as the parameter type directly rather than declaring a generic type parameter with one of the Fn traits as a trait bound.
shadow/shadowed variable
A different variable declared with the same name, the first is no longer accessible. The first is not dropped until out of scope. The second declaration does not trigger a drop.
drop/drop scope
A variable is dropped when it goes out of scope. This is called the variables drop scope. Drop is the trait which can be implemented to control behavior when dropped.
bind/bound
A variable is bound to a value. |
A module is bound to the current scope via the use statement. use p::q::{r, s} both r and s are bound in the current but not q. Use use p::q::{self, r, s} to also bind q in the current but not q.
Enumeration
An enumeration or enum is a type that can be one of multiple variants.
variant
A variant is one of the types an enumeration can take.
crate
Per the book: A crate is tree of modules that produces a library or executable.
A crate is a collection of Rust source files. It is what cargo build builds. It can either be a library crate which typically has a lib.rs or a binary crate which has a main.rs. If a package has both a main.rs and a lib.rs then you have two crates. A crate is configured using a Cargo.toml file.
library crate
A library crate
binary crate
A binary crate is an executable.
match expression
A match statement is an expression. It has a value to match against and a number of arms each with a pattern and code to execute if the pattern matches.
expression
An expression is a statement that returns a value.
currying
Instead of a function with multiple arguments, you break it down into several functions that each take a single argument and return a function taking a single argument. For the last argument you have a function which takes the final argument and returns the desired value. Each step of the currying process is basically taking a function and substituting the variable with the corresponding argument resulting in a new function. This is cool since you can generate functions at run time and store as objects and pass as arguments etc. Currying allows for easier formal proofs of program correctness.
method
A method is a function defined in the impl block for a type.
A method is called via my_object.method_name(args, ...)
Semantic Versioning
Semantic versioning or semver is of the form x.y.z where x is the major version for incompatible API updates, y is for backwards compatible functionality updates, and z version when you make backwards compatible bug fixes.
iterator adapters
Functions which take an Iterator and return another Iterator are often called 'iterator adapters', as they're a form of the 'adapter pattern'.
iterator
Iterator is a trait with method next which returns Some(Item) as it iterates thru Items and returns None when there are no more items.
method
Methods are functions attached to objects which have access to the data of the object and its other methods via the self keyword and defined in an impl block.
A method may either be a static or and instance method.
static method
A static method does not have self as the first parameter.
instance method
An instance method has self as the first parameter.
external trait/external type
A trait or type that is not defined in the current module. You cannot implement an external trait on an external type but you can implement an external trait on a type you defined and someone else can implement a trait defined in your module in one of their modules on their own type.
cargo
Rust's package manager and build system. cargo --version cargo build cargo run cargo install cargo check cargo new {name}
package
Per the book: A package is one or more crates that provide a set of functionality. There can be at most one library and any number of binary crates in the package. Each file in src/bin is a separate binary crate.
rustup
The command to upgrade your Rust compiler: rustup update
rustc
The rust compiler, rustc --version
Fully Qualified Method Calls
<str as ToString>::to_string("hi) is the fully qualified method call for qualified method call ToString::to_string("hi") or str::to_string("hi"), all of which equate to the simplest form "hi".to_string()
object safe
A trait is object safe if the concrete is reffered by &self reference tnd not by self value.
trait T1 {
fn foo(self); // not object safe,
// size of object is unknowable at compile time.
}
trait T2 {
fn foo(&self); // object safe
}
Dynamic Dispatch
The concrete type of an object is unknown at compile time.
fn foo(o: dyn MyTrait) { // this will not compile, unknown size
}
Monomorphism
fn foo(o: impl MyTrait) { // this will compile
}
use
The use keyword as in use x::y::z brings public items into scope. If z contains items (e.g. x::y::z::foo) then they can be refereced like z::foo, if z is an item (say an enum or struct) then it can be accessed using just z and not using x::y::z. You don't need to have a use statement. You can access any item that your package knows about using a full path in the code (e.g x::y::z::foo())
pub use
The pub in front of the use reexports the items from whatever you are using to appear as they are defined within this module. For example, pub use x::y::Foo in module m, means that code that has a use m can use m::Foo instead of using x::y::Foo.
module
A module is a namespace in which your items can be either public or private. A module named xyz is defined in one of three ways:
- a
mod xyz {...}block - a file
xyx.rsthat is imported into your crate via amod xzy;statement - a subdirectory named
xyzthat contains amod.rsfile, likewise imported into your crate via amod xyz;statement. Child modules can useprivateparent modules items but not vice-versa.
Troubleshooting
Published book does not show updates
Not sure how long it takes a push to github.com to show up on github.io as pages...but it may take a couple of minutes.
Pages are not generated
Every page in the book needs to be in the SUMMARY.md in order to be built.
Can't find crate for somecrate
In Rust Playground https://play.rust-lang.org only the current top 100 crates are available to be used, so you cannot arbitrarily add code and have it run inside your book on the web page.
Code block will not run in browser
For example, the Hello World will not run when you click the play button.
After a reboot, this problem magically went away. Maybe it is a browser cache issue.
Quiz
General Q&A on Rust.
Option
Q: Can you implement a trait on a type you did not define
A: Yes! But you cannot implement an external trait (one that you did not define) on an external type (one that you did not define).Q: Including code
A:Here is a anchor1:
struct Paddle {
hello: f32,
}
Here is a anchor2:
impl System for MySystem {
fn foo() {
println!("bar");
}
}
This is the anchor3.
/* I think I was playing around with a vscode anchor extension */
struct Paddle {
hello: f32,
}
impl System for MySystem {
fn foo() {
println!("bar");
}
}
Q: How to read a line from stdin?
A:// this does not work in playground since the stdin fails use std::io; use std::io::Write; fn main() { let mut s = String::new(); print!("Please enter something: "); io::stdin().read_line(&mut s).expect("Failed to read line from stdin"); println!("\nYou entered: <<{}>>", s); }
Q: What happens here upon unwrap?
let x:Option<u32> = None.unwrap();
A: The program panics! You cannot unwrap() a None, use `unwrap_or(...)` insteadlet x:Option<u32> = None.unwrap();
Q: What is the difference between unwrap_or and `unwrap_or_else`?
A: `unwrap_or_else` is lazy, `unwrap_or` is eager.
So unwrap_or_else is generally for passing a closure which is evaluated only when needed and unwrap_or is for an existing literal value known at the time of execution. `unwrap_or_else` expects a `Fn<()>` so you cannot just provide a literal or a simple expression.
`unwrap_or` on the otherhand will not accept a closure.
fn foo(k: u32) -> u32 { println!("eager ran"); 2 * k } assert_eq!(Some("car").unwrap_or("bike"), "car"); assert_eq!(None.unwrap_or("bike"), "bike"); assert_eq!(None.unwrap_or_else(||{ "bike" }), "bike"); let k = 10; assert_eq!(Some(4).unwrap_or(foo(k)), 4); assert_eq!(Some(4).unwrap_or_else(|| {println!("lazy did not run"); 2 * k}), 4); assert_eq!(None.unwrap_or_else(|| {println!("lazy ran"); 2 * k}), 20); println!("it ran");
Q: What is the another way to think of `&mut x`?
A: Ryan Levick says `&mut x` is more correctly viewed as **exclusive** reference, **not** a mutable reference.References
Blogs
- The Diagonal Device
mattgraham has some seriously helpful post on metaprogramming
- Enums of Vectors
- Debuggable print out text of closure or function name. https://exphp.github.io/ https://stevedonovan.github.io/rustifications/
YouTube Tutorials
Excercises
- Project Euler
- No solutions, just do these exercises as practice.
Notes on VSCode
Extensions
Bookmarks
Jumping back and forth in several files can be a challenge. F12 to jump to definition, then you want to jump back: if it is a different file you could close.
If you want to jump around with bookmarks
Quickly navigate using bookmarks
- Ctrl + Alt + J prev
- Ctrl + Alt + K toggle on/off
- Ctrl + Alt + L next
- Ctrl + Alt + ; clear all in current file (my mapping)
- Ctrl + Alt + , list bookmarks
Bookmarks
tools
REPL
Decent REPL for writing pieces of code
https://lib.rs/crates/evcxr_repl