Skip to content

alexozer/learn-rust

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Learn-Rust

A snapshot of my notes / learning experiments with Rust.

Chapter 1

rustup update: Update Rust rustup --version rustup doc: Open offline Rust docs in web browser

"println!()" is a macro

If "no defualt toolchain configured"

rustup install stable
rustup default stable

cargo

cargo new cargo build -> builds in /target/, creates Cargo.lock cargo run -> builds and runs main cargo check: check that it would compile, but don't produce executable (faster) cargo build --release cargo doc --open

Chapter 2

"A::b()" b() is "associated function" of A

I think: guess means "value" &guess means "immutable reference" &mut guess means "mutable reference" References are immutable by default, even if original var was declared mut

Sometimes idiomatic to break single line with multiple .method() calls into multiple lines

{} is placeholder in println!() string for next argument values

std::cmp::Ordering: another enum (Greater, Less, Equal)

shadowing variables with different types is idiomatic when converting between types in Rust

u32: unsigned 32-bit number, not bad default for small, positive number

Matching Result example:

let guess: u32 = match guess.trim().parse() {
    Ok(num) => num,
    Err(_) => continue,
};

Result types

generic Result, io::Result, etc io::Result::expect() will raise error if result is Err, else returns value of Ok (# bytes read) So type of expect() is int (or similar)

Cargo

Dependency versions: rand = "0.3.14" "0.3.14" is short for "^0.3.14" which means "any version with public API compatible with "0.3.14"

Cargo.lock: when it exists for a dependency, it uses that version of a dependency cargo update: update dependencies with new versions and update Cargo.lock Uses semantic versioning to decide how to update: "0.3.14" will only update to a higher version of "0.3." To get something like "0.4." you need to update Cargo.toml (and then cargo update)

Chapter 3

Variables and mutability

Shadowing variable declarations is not completely taboo, even when keeping the type can be used to do transformations without needing to make variable mut so it stays not mut afterwards

Constants

must have type annotation must be assigned to constant expression can be declared in global scope too

Data types

Type inference can happen a lot, sometimes a lot of types are possible and annotation required

Has integer types i8 - i128, u8 - u128, isize and usize

23_523 integer 0xff hex 0o77 octal 0b1100_0110 binary b'A' byte (for u8)

Debug-compiled Rust panics on overflow! You shouldn't rely on implicit wrapping, using Wrapping type

f32 and f64: float types f64 is default

Basic math operations + - * / % work as expected (% works for float too!) bool stuff: values: true and false, type: bool

char is 16-bit Unicode Scalar Value

Multiple assign doesn't really work except for let with pattern matching

Tuples

let tup = ("uh oh", 3, false);
let (x, y, z) = tup;
println!("x: {}, y: {}, z: {}", x, y, z);

Can also index tuple elements with tup.0, tup.1, etc

Arrays

Fixed-length (vectors are not) normal square bracket notation for literal

types: [; ] [i32; 5]

[3; 3; 3; 3; 3] is same as [3; 5] array indexing normal syntax like a[2]

Functions

snake_case is used instead of camelCase (for variables too)

  • function definition order doesn't matter

Must declare types in function signature

Rust has both statements and expressions (normal let is a statement, fn is a statement, etc) blocks are expressions! last "statement" without semicolon is expression for whole block does not having expression at end of block mean block is unit? nope it's "empty tuple"

Most functions return this way (without "return") at end

Comments

Only has // and "documentation comments" "more often" you see // above line than next to it

Conditionals

if has no parens Must be bool, no truthy stuff if is an expression!!

let number = if true {
    2
} else {
    9
};

Loops

Unbounded loop: loop loops are expressions too!! break while loops like you'd expect

for in { } Range object used for number ranges with same for structure

.rev() reverses sequences or something? 0..10 sequence / range (endpoints can be arbitrary expressions) (0..10).rev() reverses the sequence (not sure about exact mechanics of what is going on here yet though)

Chapter 4

References and Borrowing

String literals are different than String let s2 = s // invalidates s ("move") s.clone() will do the deep copy and not invalidate s

  • clone() is explicit call which is good since it's generally more expensive and complex

None of the above matters for simple stack-only types which are all always copied directly anyway

  • Copy trait applied to these types
  • If Drop trait is applied to type (something special needs to happen when goes out of scope), it's a compile error to apply Copy
  • Tuples are not Copy if they contains a value that is not Copy, otherwise they are

Passing non-Copy to function takes ownership

  • Doesn't seem to occur with println!() macro, maybe that has something to do with why it's a macro? Not sure

Function return values transfer ownership as well

print_len(&s) // pass reference to String s fn print_len(string: &String) // takes reference to String (does not own, so does not free when goes out of scope) This is called "borrowing" All references are immutable by default

More than one mutable reference to the same thing can't be used at once!

  • Seems like you can create more than one mutable reference, but using either one when both exist does NOT work
let r1 = &mut s1;
let r2 = &mut s1;
println!("{}", r1);
  • Also can't have a mutable and an immutable
  • Can use curly braces to separate scope of references

Rust won't let you create dangling references like returning reference to something destroyed at end of function for example

Slice

enumerate() returns tuple of index and reference to original item

s.as_bytes(): convert string to byte array for searching String slices: &s[5..10] Can drop first or second slice value to mean start or end

String slice type is &str (immutable reference)

can't mutate s because then a mutable and immutable reference would be in use at the same time!

Type of string literal is &str too!

Often better to have function take &str instead of String so literals can easily be used then pass String's as &s[..] Also, you can slice &str as much as you want to get &str

Also works for arrays: let a = [4; 10]; let b = &a[3..5];

Chapter 5, Structs

Example

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

When var and struct field have same name, can just use "username," to init struct field in struct init

struct update syntax (like ocaml "with") Type {assignments ..orig_struct}

Unit-like structs are structs with no fields Often used when you want to define trait on type but don't want to store any data in type itself

Tuple-like structs don't have field names, just ordered types Useful when you want to give tuple type a name and make different than other tuples with same types of elements, but don't want to name elements

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

In order to store references in structs, you need to learn about lifetimes (like &str)

Printing: "{:?}" means "debug formatting"

to auto-implement Debug trait: "#[derive(Debug)]" above struct definition

Methods

first param is some variant of "self" like "&self" (don't need explicit type annotation, but must be called self) method can also take ownership of self and borrow mutably

"main benefits of methods with auto self parameter is organization in 'impl' is organization"

Rust doesn't have -> for references for example; it automatically takes references as it needs with "." the alternative is something like (&mut a).blah(&b) (ew)

Can have function in imp block without self argument, it's an "associated function" and can be accessed with ::func()

Can also use multiple impl blocks per type

Chapter 6: enums and pattern matching

Rust basically has variants like OCaml ("enums with data attached")

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

Move has an anonymous struct in it For each of the enum entries, you could separate it out and place "struct" in front of it and it would work as a struct (for Write and ChangeColor they'd be tuple structs, Quit would be unit struct)

You can also define methods on enum types inside impl

Rust has option like ocaml: Option (with Some(T))

eventually read more about Option: https://doc.rust-lang.org/std/option/enum.Option.html

Match

Use curly braces for more than one line in arm, otherwise omit them Destructuring with match (at least for tuple struct-like enums) seems to work the same as OCaml In Option matches in Rust book, None arm seems to be placed above Some arm (convention) match is exhaustive just like OCaml _ is also catchall placeholder like OCaml

If-let

Syntactic sugar for single-case match

if let Some(3) = some_u8_value {
    println!("three");
}

Else works with it too

Module System

Packages and Crates

Crate is library or binary Package contains 1+ crates and has Cargo.toml At most 1 crate in package is library So cargo new my-project creates a package named "my-project"

If path src/main.rs exists in package, it means a binary crate with same name as package exists in package with crate root at src/main.rs If path src/lib.rs exists, then package also has a library crate with same name as package and crate root as that Can have both of those files and package will have both a binary and a library crate Can create multiple binary crates by placing files in src/bin; one binary crate for each file there

Modules

Crate root creates module with name "crate" which serves as root for modules within Anything in current or parent module relative to you is accessible, everything in a child module (relative to current) is private by default use "pub" prefix to make thing publically accessible Use "super::" to access relative to parent module Useful because even if code (and referenced code with super::) are moved inside a new module, it'll still work Structs can be made public, the fields must be designated public individually though Enum variants are all public if enum is public

Example

mod plant {
    pub struct Vegetable {
        pub name: String,
        id: i32,
    }

    impl Vegetable {
        pub fn new(name: &str) -> Vegetable {
            Vegetable {
                name: String::from(name),
                id: 1,
            }
        }
    }
}

fn main() {
    let mut v = plant::Vegetable::new("squash");

    v.name = String::from("butternut squash");
    println!("{} are delicious", v.name);

    // The next line won't compile if we uncomment it:
    // println!("The ID is {}", v.id);
}

Use

Use use to shorten module paths:

mod sound {
    pub mod instrument {
        pub fn clarinet() {
            // Function body code goes here
        }
    }
}

use crate::sound::instrument;

fn main() {
    instrument::clarinet();
    instrument::clarinet();
    instrument::clarinet();
}

Relative paths with use currently require using self:

mod sound {
    pub mod instrument {
        pub fn clarinet() {
            // Function body code goes here
        }
    }
}

use self::sound::instrument;

fn main() {
    instrument::clarinet();
    instrument::clarinet();
    instrument::clarinet();
}

Using absolute path with use makes it easier to refactor when moving code to another module. Rust book authors generally use "crate::" absolute paths with use more often as code using use more likely to be moved around module tree independently from code being called

Idiom: use module path for functions (makes it clearer that function isn't locally defined) But, use full path of thing for structs, enums, and other items

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
}

Not a strong reason for the second one, it's just what ppl have gotten used to writing

The exception is if use would import two things with same name; then just import module

use std::fmt;
use std::io;

fn function1() -> fmt::Result {
}

fn function2() -> io::Result<()> {
}

Can also use as like in Python It's idiomatic to use as to prevent name conflict

Use pub use to re-export the new name that use brought into scope (it's private to the outside by default, even if the thing given to use is public)

Can factor out paths from same parent path:

use std::{cmp::Ordering, io};

To bring both path and direct subpath into scope, use self:

use std::io::{self, Write};

Can also use '*', bad to use in general for the usual reasons Often used in testing though to bring stuff that's tested into scope in tests module Also used for preludes, which are sets of commonly-used things in crate which are picked by estimated frequency of use All Rust modules contains use std::prelude::v1::*

About

Snapshot of some of my Rust notes

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages