Notes while learning rust
The Rust Programming Language (book) Notes: https://doc.rust-lang.org/book/title-page.html
-
Piping 'rustup' directly into a shell. Trendy but also questionable.
-
Weird that macros require the
!
suffix. Are users going to have to depend on memory to know when to use a macro vs function? -
Cargo for creating projects seems much nicer than pip+virtualenv. It's more like npm at this point.
-
Create a new project using
cargo new hello_cargo
-
This creates a
Cargo.toml
file and asrc/
directory and initializes agit
repository. -
What does the
edition
field inCargo.toml
correspond to? It defaults to "2018". Version of rust package configuration? -
Defaults to semantic versioning. Grabs your git config email address and inserts it.
-
cargo build
builds a file and places it intarget/debug
by default. -
cargo run
automatically runs it, but it will also automatically detect other changes. -
A whole bunch of additional object files & others are created in the
target/debug
directory. -
Includes an
Info.plist
file andResources
directory. -
hello_world
has large ELF-looking binary placed intarget/debug/deps/hello_cargo.dSYM/Contents/Resources/DWARF/hello_cargo
and it's HUGE. 961kB aka 1MB. -
What's the
.dSYM
file used for? -
Other
hello_world
binary is intarget/debug/deps
and is 309kB. Still huge. -
Contains other files in a
.fingerprint
directory. Also contains aninvoked_timestamp
file which has anmtime
of when the build was started. -
Has a bunch of
.o
object files intarget/debug/incremental/hello_cargo-somehash/some-uuid-looking-thing
-
Has a
.lock
file in the same directory. -
Has some
dep-graph.bin
,query-cache.bin
, andwork-products.bin
files in addition to the object files. -
dep-graph.bin
hasRSIC 1.45.2 (d3fb005a3 2020-07-31)
header. -
query-cache.bin
has the same but also has mention ofprintln
and other functions, as well asrust_eh_personality
(?). -
work-products.bin
has the same header but the file is tiny and doesn't have much else. -
cargo check
does a dry-run without producing a binary. Faster than runningcargo build
to check if it builds or not. -
Maybe setup a vim script to autorun
cargo check
on file save. -
Cargo appears to search parent directories for a
Cargo.toml
file if you're in thesrc
directory. -
cargo build --release
to build a release version with optimizations. -
cargo run
still runs the dev version.cargo run --release
runs the release version.
-
Annoying that you have to create a new string with
String::new();
instead of""
shorthand. Is it trying to be purely functional? -
use
std::io
andio::stdin(
. Are there explicit imports/namespacing or do you have to depend on tooling to know which functions a library provides? -
So
read_line(&mut guess)
requires a reference. Why? -
Not 100% sure on 'the prelude' and
std::io::prelude
yet. https://doc.rust-lang.org/std/prelude/index.html -
All variables (defined by
let
) are immutable without amut
keyword. -
String::new
calls the associated function (not method) on the String type (not class) -
&
works like a reference, presumably like in C and C++. Using&mut
allows you to modify the reference. -
.read_line
returns anio::Result
which is an enum (has a fixed set of values). Result has variantsOk
andErr
. -
Ok is handled if operation was successful
-
Err if it wasn't
-
.expect()
causes program to crash (fatally) and display the message you give it if an error occurs. -
Without calling
expect()
the program will run but it'll complain that you haven't handled the Result. -
println() accepts curly-braces like Python's .format strings (but not like f-strings). I'd prefer f-strings.
-
Dependencies need to be manually added to Cargo.toml?
-
Good that it only auto-upgrades patch versions when you run
cargo update
. -
Does Cargo.toml also get updated with dependency versions?
-
Cargo fetches packages from the package registry - crates.io
-
cargo build
checks if your files have changed before recompiling, and doesn't do anything if they haven't changed -
Cargo.lock file is created (similar to yarn/npm locks) to try and ensure reproducible builds
-
use rand::Rng
. Rng is apparently a trait which defines methods. Must be in scope to use those methods. Ch10 for traits. -
rnd::thread_rnd().gen_range(1, 101);
. thread_rnd and gen_range aren't great function names. -
cargo doc --open
should build and open docs for what you currently have. -
match
keyword appears to be like C's switch statement at first, but more powerful. -
Guido van Rossum has some good notes on it for an upcoming PEP (622): https://github.com/gvanrossum/patma/
-
Passing a reference to
secret_number
in toguess.cmp
seems verbose. -
Branches/cases instead a match are called arms
-
use std::cmp::Ordering
for access toOrdering
-
Ordering::Less
,Ordering::Greater
,Ordering::Equal
also seems like a super-verbose way to do this. -
Ordering
is also an enum. -
Nice that they appear to support arrow functions
-
Need to provide type annotation when defining
guess
as it's not able to figure it out on its own. -
let guess: u32 = guess.trim().parse().expect("...")
all on one line looks horrible.expect()
isn't returning a u32 is it? Behaviour is weird here. -
let guess
rebinds the earlierguess
of String type from above using shadowing. Shadowing seems like something implicit, whereas other parts of Rust are explicit. -
loop
's syntax is nice -
The
match
expression is interesting. Arrow functions allow appear to allow for really concise handling. -
_
is used as a throwaway like in Python & JS. e.g.Err(_) => continue
. Somewhat weird thatOk(num) => num
implicitly returnsnum
butErr(_) => continue
triggers thecontinue
statement. -
Switch from
expect
tomatch
in order to handle errors. -
Result
is apparently generic but it has specific subtypes, e.g.io::Result
. -
Tried
cargo install rand
- connects to GitHub. Takes a really long time to update the crates.io index. -
Then received
error: specified package rand v0.7.3 has no binaries
. -
Adding to
Cargo.toml
then runningcargo build
worked. Weird that it would give an error for the other one though. -
Cargo.lock
contains other dependencies and checksums. -
Removing it from
Cargo.toml
and rerunningcargo build
removes it fromCargo.lock
but doesn't say that it's removing it. -
Adding
rand = ""
installs the latest version. -
cargo update
does nothing if you have the latest version. I wonder if it has to be continually updated for each new package that receives an update, and how frequently it has to be updated, as it took forever. -
Doesn't complain about camelCase variables.
-
Calling
rand::thread_rng()
withoutuse rand::Rng;
gives "use of undeclared type or module "rand". -
Tried
use rand
and got "error: norand
external crate" whilerand
isn't installed. -
Installed it and it lets me implicitly call
rand::rand_thread
but then complainsrand::rngs::thread::ThreadRng cannot be formatted with the default formatter
when passing it toprintln!()
. -
Tried
use rand::Rng
and then usinglet secret_number = rand::Rng()
and goterror: not a function, tuple struct or tuple variant
. -
This kind of importing of
Rng
and using ofthread_rng
without any reference toRng
is confusing. -
Tried
let secret_number = rand::();
and gotexpected value, found crate 'rand'
. -
Complains if I do
let secret_number = rand::random()
and needs a type hint. -
Using
rand::random()
but the linter still complains that I'm not usingrand::Rng
. Strange. -
Tried using
let secret_number: rand::rngs::thread::ThreadRng = rand::thread_rng().random()
and it complains thatthread
is a private module. Cool that modules can have different access mechanisms. -
thread_rng
is a Thread-local random number generator. -
gen_range(1, 101)
behaves like Python'srange()
with the101
being exclusive. -
cargo doc --open
builds docs for each crate/module. Very cool. Python's needs to look better too. -
The docs generated by this look incredible.
-
You can also directly browse the source from inside the browser with nice syntax highlighting. Python needs this.
-
It highlights the specific function you've clicked too.
-
Also previews the function declaration. This is awesome.
-
rand::distribution
docs mentions that all implementations are expected to be immutable, so you don't need to worry about thread safety. -
Some weird-ass syntax like
impl<'a, T, D: Distribution<T>> Distribution<T> for &'a D {
in rand'smod.rs
. I guess I'll learn what these apostrophe's are later. -
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> T {
question mark prefix and(*self).sample(rng)
are weird too. Is the*
a pointer dereference? -
Tried
let secret_number = rand::thread_rng().gen_range()
and got"the trait rand::distributions::uniform::SampleUniform is not implemented for std::string::String"
plus"the trait bound std::string::String: rand::distributions::uniform::SampleUniform is not satisfied"
. All because of missing arguments togen_range()
. -
Wrote
let guess u32 = guess.parse()
and got `expected one of ':', ';', '=', '@', or '|'. Trying each. -
use of undeclared typ or module Ordering
when trying to use them withoutuse
ing it. -
let guess@ u32: guess.parse();
works. -
guess.trim()
returns a string. Trims newlines. -
Just noticed that Rust doesn't (yet) have an equivalent to
input()
or a function that simultaneously prints a line and waits for input. -
Got an error when typing 123 as the input? It compiled fine then said
ParseIntError: { kind: InvalidDigit }
. -
.parse()
required a type hint for type inference. -
Had to run with
RUST_BACKTRACE=1 cargo run
and it's got a huge backtrace which reminds me of C++ template debugging. -
Required
RUST_BACKTRACE=full
for full trace. Still no clearer. -
This is constantly panicking. No idea why right now as my code appears to be identical to the book.
-
Was caused by not calling
.trim()
before callingparse()
. Rust should definitely provide a better error message if it finds a newline while trying to parse a number. -
match
syntax can be strange it seems. I guess it takes a while to get used to. -
_
behaves like a catchall. I wonder what happens if you only specify a specific error type. -
Struggles with match keyword when trying to make
Err(_) => { ... print something and return a value }
instead. -
Also can't explicitly return
continue
from here in the event of an error. This must not have been getting triggered earlier. -
Getting another shitty confusing error. It doesn't like something about the code I've got and is saying
match
is an unexpected token,expected one of '.', ';', '?', '}', or an operator
. -
Accidentally somehow deleted a
;
from a preceeding line and it was telling me where it should be. I'm an idiot. -
Printing
Err(x)
hasx
implicitly converted into the error string. Useful for debugging. Seems to be generic catchall for all errors. I wonder how multiple 'exception types' will be handled (as Rust doesn't appear to have Exceptions, only panic and Result) -
Combining two statements with
return continue;
gives a compiler warning:Unreachable code
for thereturn
statement andany code following this expression is unreachable
for the continue. So it's finding and parsing thecontinue
before it finds thereturn
statement.
-
Try removing the
!
from println. - Suggests "use!
to invoke the macro". -
Try removing the
mut
from&mut guess
-
Try change the name of
guess
-
Try pass in just
read_line(guess)
. - Suggests mutably borrinwg using a reference. -
Try adding
char
type annotation toguess
definition. - Smart enough to check types both when defined and when used later on. -
Try use the
\n
character when printing. -
Try the
print
statement instead of theprintln!
macro. -
Try replace
String::new();
with a string literal - Empty string literal is &str -
Try replace it with an empty character literal - Empty character literal just says 'empty char'
-
Try replace it with a character literal - expected
&mut std::string::String
but found&mut char
-
Try put something in the curly-braces. - 'Positional arguments are zero-based' error. Kinda vague.
-
read_line
expects aString::new()
reference (object?). Passing a mutable string-literal doesn't -
Has built-in linting for unused variables. Suggests using
_
underscore prefix for unused variables. -
Also catches when you change a variable but don't later use it.
-
Suggests if a mutable variable is not mutated and tells you it doesn't need to be mutable. Very cool!
-
String::new() == ""
so it's strange that you can't uselet mut guess = "";
and pass it toread_line
. -
String::new("some string")
also doesn't work. This is strange behaviour for teaching beginners about strings. -
How should users know when to use
String::new
and""
? -
Try enter a string instead of number. - Implicitly converts the input to a string.
-
Try leave off
.expect()
call. - Warns aboutResult
possibly beingErr
variant and it needing to be handled. -
Try leave off arguments or give
expect
an integer.
-
Mentions
mut
keyword and the error you see if you try to reassign an immutable variable. -
const
keyword for defining constants. Can only be set to an expression (not the result of a function call). Type must be annotated. -
Can use
_
separator for numbers like with Python. e.g.const MAX_POINTS: u32 = 100_000;
. -
Does Rust complain if your constants aren't ALL_CAPS? - It doesn't error but it warns.
convert: max_pOiNts to MAX_P_OI_NTS
. -
Separators also make no difference.
100_000 == 1_0_0_0_0_0
. -
Shadowing requires reusing the
let
keyword, and doesn't need themut
keyword. Also lets you change types. Be very careful when using it. Probably safest to keep variable declarations at the top of scope, like with C. -
"Rust is statically typed so it must know the types of all variables at compile time."
-
Scalar types: i8/u8, i16/u16, i32/u32, i64/u64, i128/u128, isize/usize. The latter depend on architecture, e.g. 32 on 32-bit arch, 64 on 64-bit arch.
-
Uses two's-complement like other languages.
-
Integer literals: 98_222, 0xff, 0o77, 0b1111_0000, b'A' (Byte, u8 only).
-
In debug-mode Rust checks for integer overflows at runtime. In release mode it doesn't, but it performs two's-complement wrapping.
-
Float types: f32, f64.
-
Operators: +, -, *, /, %
-
Boolean types: bool for type annotation, true/false for values.
-
Character types: 'x', supports Unicode.
-
Compound types: Tuple:
let tup: (i32, f64, u8) = (500, 6.4, 1);
-
Destructuring:
let (x, y, z) = tup;
. -
Can also use implicit types:
let tup = (500, 6.4, 1);
. -
Accessing via index:
x.0
,x.1
, etc. I wonder how to loop through them by index? -
Array type:
let a = [1, 2, 3, 4, 5];
- Have fixed lengths. Allocates data on the stack rather than heap. Not as flexible as vector types. -
Can explicitly specify type and size:
let a: [i32; 5] = [1, 2, 3, 4, 5];
-
Can also specify default values:
let a = [3; 5];
generates[3, 3, 3, 3, 3]
. -
Accessed via index. e.g.
a[3]
. Doesn't check for invalid index at compile-time but notices the invalid index at runtime..
-
snake_case
for function and variable names -
Rust doesn't care where you define your functions
-
What happens when you try to return from a function which you haven't specified a return type? - Says 'possibly return type missing here?'
-
statements
are instructions that perform some action that do not return a value -
expressions
evaluate to a resulting value -
You can't assign a
let
statement to another variable. e.g.let x = (let y = 6);
. - Says 'variable declaration usinglet
is a statement'. -
Can't write
x = y = 6
in Rust. -
Can use them in curly braces if you use block-scope. e.g.
let y = {
let x = 3;
x + 1
};
-
Function return types are annotated with
-> type
. e.g.fn five() -> i32 { 5 }
-
Forgetting to return from a function gives 'expected
i32
, found()
. -
Rustlings notes: Returning Ok(()) from a function looks weird.
- Comments all begin with
//
. There are no multi-line or block comments. Just prefix everything with//
-
if
expressions don't have surrounding parentheses. e.g.if number < 5 { ... } else { ... }
-
Blocks of code associated with conditions are called arms in Rust.
-
No truthiness in Rust. Conditions must evaluate to booleans. Says 'expected
bool
, found integer' otherwise. -
No implicit type conversion/coercion in conditional
-
Can use
if/else
in alet
statement. e.g.let number = if condition { 5 } else { 6 };
. -
Values returned by different arms must all be the same type. - Get 'expected integr, found
&str
otherwise. -
Rust has 3 loop types.
loop
,while
, andfor
. -
loop
runs until you explicitly tell it to stop -
The
break
keyword supports returning values from a loop. e.g.let result = loop { ... }
thenbreak counter * 2
inside the loop. Pretty cool! -
while
loops work as expected. e.g.while number != 0 { ... }
. -
for
loops require creating an iterator. e.g.let a = [1, 2, 3];
thenfor element in a.iter() { ... }
. -
Sequences and reverse loops can be done with
for number in (1..4).rev() { ... }
- Convert temperatue between Fahrenheit and Celsius
- Generate the nth Fibonacci number
- Print the lyrics to the Christmas carol song "The Twelve Days of Christmas", taking advantage of the repetition of the song.
-
s.push_str(", world!"); // push_str() appends a literal to a String
. Why push_str? Can't it infer a string from the argument? -
Rust has 2 string types. String for heap-allocated strings and str for both literals and slices.
-
Use String::from to create a String from a string literal.
-
:: operator is used for namespacing.
-
Copy trait can be used only if the Drop trait hasn't been used.
-
Rust automatically calls the drop function when something goes out of scope.
-
Types that implement Copy trait (stack-based): Integers, Booleans, Floats, Chars, Tuples that contain types which implement Copy.
-
Functions take ownership of their arguments but can give it back with return values.
-
Still strange to have implicit returns (without the return keyword) when everything else in the language is explicit.
-
Use references when you don't need to take ownership.
-
& for references (similar to C++)
Copy vs Clone types - https://stackoverflow.com/questions/31012923/what-is-the-difference-between-copy-and-clone
Named arguments don't work for constructors? e.g. Point::new(x: 10, y: 10);
-
Move semantics 2 1st way: vec0.clone();, 2nd way: borrow (using &) then make clone and return new vec, 3rd way: pass in &mut vec0 to fill_vec and change args to make it a mutable reference. Make vec2 mutable, remove return value and change function signature, then finally make vec1 a clone of vec0 after filling vec0.
-
Primitive types 2: is_numeric() only exists on characters, not on strings.
-
Stupid: Parsing a float in Rust:
s = "3.14"; s.trim().parse::<f64>().is_ok()
- Struct are similar to those in C.
- Fields and instances are names reused from OOP.
- Don't need to use the same order as field names when creating an instance of a struct.
- dot-notation for accessing fields. e.g.
user1.email
. - Entire instance must be mutable when creating an instance. Single fields in a struct can't be mutable while others aren't.
- Constructor name isn't used in book yet. Functions can have any name so presumably you can also have multiple constructor types.
- Must return the same type as the struct. e.g.
fn build_user(..) -> User {
for aUser
struct. - Instantiate the struct with curly-braces inside the function and it's implicitly returned.
- Can avoid
email: email,
etc using similar trick to ES6 by just writingemail
if field and parameter share same name. - Struct-update syntax works like ES6' spread operator. e.g.
..user1
at the end of instantiating a struct to copy additional fields. - Tuples can't be iterated over due to rust being an expression-based language.
- Tuple structs look like:
struct Color(i32, i32, i32);
thenlet black = Color(0, 0, 0);
- Unit-like structs without any fields "behave similarly to
()
. e.g.struct AlwaysEqual
thenlet subject = AlwaysEqual;
. Purpose? - Used when you need to implement a trait on some type but don't have any data that you want to store on the type.
- Ownership. Want stures to own their data, but can also store references using lifetimes (Chapter 10).
Notes from Rustlings: 3 struct types: C, tuple, and unit(? no docs on this, seems to just be like the empty set?).
- Structs require
#[derive(Debug)]
outer attribute to be displayed with{:?}
withprintln!
. - Use
{:#?}
for prettier debug output. - Can use
dbg!(&rect1)
to display additional error information, but it prints by default tostderr
instead ofstdout
. Extremely helpful! - There are many attributes other than
derive
and many traits other thanDebug
.
- Used for turning
area
function into a method defined on theRectangle
type. - Methods are defined within the context of a struct, an enum, or a trait object.
- First parameter is always
self
(like in Python). - Put them inside an
impl
block. - Call them just like you would in OOP languages.
- Using
&self
is short forself: &Self
. - Need the
&
in front ofself
to indicate that the method borrows theSelf
instance. - Methods can take ownership of
self
, borrow it immutability (like above), or borrow mutably (with&mut self
). - Rare to borrow self mutably unless method transforms self into something else and you want to prevent caller from using original instance afterwards.
- Rust can have a method with the same name as a property. e.g.
width
property on a type andwidth()
method on it too. For a language focused on safety and unambiguity, this seems dumb to allow. It's a way for rust to do getters but it's also implicit rather than explicit. - Rust has no
->
operator like C++. Has automatic referencing and dereferencing. When you callobject.something()
rust automatically adds&
,&mut
, or*
so object matches the signature of the method. - Use same
other
parameter name for operator overloading style of comparing two types like you would with something like Python. e.g.fn can_hold(&self, other: &Rectangle)
- All functions defined within an
impl
are associated functions. Functions withself
are methods but ones without are not. e.g.String::from
. - Associated methods are often used for constructors.
- Use the
::
syntax to call an associated function. e.g.fn square(size: u32) -> Rectangle { ... }
thenlet sq = Rectangle::square(3);
. Function is namespaced by the struct. - Can use multiple
impl
blocks.
- Define enum with the expected syntax:
enum IpAddrKind { V4, V6 }
and create them likelet four = IpAddrKind::V4;
- Can also be used like
enum IpAddr { V4(String), V6(String), }
and `let home = IpAddr::V4(String::from("127.0.0.1")); - stdlib has IpAddr enum types.
- Can define methods on enums just like with structs using
impl
. Option
enum type- Rust has no null type
Rustlings notes: Enums are defined without using strings or defining names. Two different syntaxes for defining enum types. struct-like with argument names & curly braces, and function-like without.
- .unwrap() panics if the function returns an error. Useful in early stages of development before proper error handling is in place.
-
Rustlings notes: AppendBar implementation uses self + "Bar". Isn't "Bar" a CString/str? If so shouldn't the compiler complain when trying to append it? self + String::from("Bar") fails yet the function signature returns a String and not an str.
-
Kind of cool that traits can add methods to built-in types.
-
Coherence/Orphan Rule (ch10.02) prevents overriding stdlib methods that are out of scope.
-
Trait Bounds: Can a method not call methods on another type without specifying the traits they implement? e.g. with notify() in chapter 2.
-
Multiple trait bounds using the where keyword.
- Read arguments with
std::env
- let args: Vec = env::args().collect();
- Access with &args[1];
- Read files with
std::fs
andfs::read_to_string(filename)
- Functions that return tuples can have their values accessed via name.
- e.g. If a function returns (query, filename) and you save to a
config
variable, can access withconfig.filename
.
- Rustlings notes: *i *= 2. Interesting syntax for iter_mut in collections/vec2.rs.
-
Rust only supports 1:1 threads by default (M:N threads are supported by libraries).
-
use std::thread and thread::spawn, giving it a closure with || to create a thread.
-
Save result as a handle and use handle.join().unwrap() to force execution to wait for threads to end.
-
Use move keyword to move value into thread instead of having thread borrow it.
-
use std::sync::mpsc for Message Passing (multiple producer, single consumer).
-
Thread must own tx to use it.
-
tx.send() returns a Result<T, E>
-
Ownership of a value is transferred once it's been sent down a channel.
-
rx has recv and try_recv. try_recv is non-blocking.
-
use std::sync::Mutex for mutexes.
-
Rc is not thread-safe. Use Arc (atomic).
-
See stdlib docs for std::sync::atomic for more details.
cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project
cd my-project