# -*- mode: org -*- Rust memos (based on the stable version, December 2016) * Environment - package manager (cargo) takes care of compiling/running etc. (see below) - backtrace printed when the environment variable RUST_BACKTRACE=1 - rustdoc generates documentation from doc-comments * Basic Syntax - basically as in C/C++, but... - semicolons separate expressions - { ... } is an expression - attributes instead of compiler directives ** Documentation - /// is a doc-comment (markdown syntax) of what follows (e.g. before functions) - //! is a doc-comment (markdown syntax) of what encloses (e.g. in modules) - first line of a documentation => short summary of functionality (one sentence) - sections (introduced by #) => Panics, Errors, Safety, Examples - normally the Example section also works as a test (using assert!) - code blocks are between lines of ``` - ignored/should_panic tests are introduced with ```rust,ignored / ```rust,should_panic - lines in code blocks that start with # are omitted from the docs - documentation can be written in .md (markdown) files [but need to start with: % Title] ** Attributes - applies to what follows => #[attribute(parameters)] - applies to the enclosing item => #![attribute(parameters)] - e.g. #[cfg(flag)] => compiles the following code only when flag is true - helper functions: not, any, all, etc. => #[cfg(any(unix, windows))] - cfg!(flag) expands to the flag's value in compile time - full list: https://doc.rust-lang.org/stable/reference.html#attributes * Variables - let pattern = expr; - immutable by default; for mutable => let mut var = val; - type annotations => let var: type = val; - only declaration => let var[: type]; - function type annotation syntax => fn(arg_types...) -> ret_type - scope: block (curly braces) - shadowing is allowed (but shadowed variables still live until their block's end) - casting => var as type, e.g. x as i64, &mut y as &mut Foo - unsafe casting => std::mem::transmute [in an unsafe block] ** Constants and Static Variables - (inlined) constants => const var: type = val; [type annotation is required] - static variables => static var: type = val; [type annotation is required] - modifying a static mut is unsafe => can only be done in an unsafe block - types in a static variable must be Sync, and cannot be Drop * Pattern Matching - multiple patterns are join with '|' - matching structs: Point { x, y } or Point { x: x1, y: y1 } or Point { y, .. } - similarly on tuples and enums - ignored bindings are named '_' - 'ref' and 'ref mut' creates references inside the pattern - '...' creates ranges, e.g. 3 ... 8, 'a' ... 'z' - '@' creates bindings - guards are introduced by 'if' * Ownership, Borrowing & Lifetimes - variable bindings have ownership => end of binding frees resources - each resource has only one binding (assignment makes the right-hand side invalid) - exception: types with Copy traits (e.g. bool, char, numeric types, arrays/tuples of Copy types) - references "borrow" resources (annotated by '&' in the argument type and at the calling site) - de-referencing by '*' - references are immutable by default - mutable references => &mut - borrowing rule #1: references should have shorter lifetime than the owners => when in the same block, references should be declared later (freeing is in opposite order) - borrowing rule #2: when there is a mutable reference, there cannot be any other ** Lifetimes - lifetime of references can be specified with <> - functions => fn foo<'a>(x: &'a mut i32) { ... } - multiple lifetimes => fn bar<'a, 'b>(x: &'a str, y: &'b str) -> &'a str { ... } - methods => impl<'a> Foo<'a> { fn x(&self) -> &'a i32 { ... } } - structs [containing references] => struct Foo<'a> { x: &'a i32, } - special lifetime: 'static (e.g. global constants, string literals) *** Elision rules - input lifetime: function parameter - output lifetime: function return value - rule #1: elided input lifetimes are distinct lifetime parameters - rule #2: if there is 1 input lifetime [can be elided], it is assigned to elided output lifetimes - rule #3: if one of the input lifetimes is &self or &mut self, the lifetime of self is used - in any other case, output lifetime cannot be elided * Functions - fn foo(args...) -> ret_type { ... } - arguments need type annotations - no return type => return value is () - diverging (never returning) functions => return type is '!', can be used as any type - return value is the last _expression_ (=> so no semicolon after it) - explicit "early" return => return value; * Traits - interfaces / generic functions - trait Foo { fn bar(...); } and then impl Foo for Type { fn bar(..) { ... } } - default implementations can be given in trait - parent trait can be given => trait Foo : Bar { ... } - referring to the type of the instance => Self - generic function parameters can have traits: fn baz(...) { ... } - multiple traits can be joined by '+', e.g. fn foo(...) { ... } - this can be written also with where: fn foo(...) where T: HasThis + HasThat { ... } - traits can be implemented only if the trait or the function/type it is for is in the same crate - if two traits have the same function name, we can call them by Foo::bar() etc. - also ::bar() [for methods these require to pass self explicitly] - type annotation can be given at the calling site => foo::(...) - some traits can be derived => #[derive(Debug)] - only these: Clone,Copy,Debug,Default,Eq,Hash,Ord,PartialEq,PartialOrd ** Trait Objects - static dispatch => for dynamic, use trait objects &Foo or Box - trait objects can be of any type that implements that trait (known only at runtime) - trait objects can be created only of object-safe traits (no type parameters, does not use Self) ** Associated Types - type variables inside the trait => type T; - these can also have trait assumptions => type T: HasThis; - with this, functions using this trait need not be generic over these types - in an implementation of the trait, we can assign concrete types => type T = ImplType; ** "Drop" Trait - drop(&mut self) - called when the object goes out of scope ** "Iterator" Trait - element type + next() member function - iterator adapters (map, take, filter, collect, etc.) - iterators/adapters are lazy ** "Borrow" Trait - std::borrow::Borrow, implements borrow(&self) -> &T - owned or borrowed type - accepts both &T and &mut T - String is Borrow [so String and &str can be used interchangably ** "AsRef" Trait - std::convert::AsRef, implements as_ref(&self) -> &T - used when converting explicitly to a reference (<=> Borrow is for accepting different types) ** Operator Overloading - operator traits are in std::ops - e.g. implement Add for + - overloading the "*" dereferencing operator => Deref trait * Closures - let f = |var1: type, var2: type| -> ret_type expr; - type annotations are optional - the whole closure should be mut if it changes the environment => let mut f = ... - closed variables are borrowed by default => move |...| { ... } takes ownership - move closures have their own stack frame => can be returned from functions - closures are implemented as traits; type annotation: Fn(types...) -> ret_type - for dynamic dispatch, use &Fn(...) and &|...| { ... } * Methods - impl Type { fn foo(&self, args...) -> type { ... } } - the first argument can be self, &self or &mut self - associated ("static") functions do not have self => called as Type::foo(...) - one impl block can contain several methods; any number of impl blocks can be used * Types - bool => true, false - char => 'a' etc., 32-bit UTF-8 ** Numeric - i8,i16,i32,i64 signed integers - u8,u16,u32,u64 unsigned integers - isize,usize signed/unsigned integer of architecture-dependent size (= pointer-size) - f32,f64 floating point reals ** Arrays - type annotation: [elem_type; length] - static length - let a = [1, 2, 3, 4, 5]; - [3; 5] means [3, 3, 3, 3, 3] - length => a.len() - n-th element => a[n] (indices start from 0) - slicing (subarray) => &a[begin..end] (end is not included) - slicing of the whole array => &a[..] ** Vectors - dynamic allocation on the heap (resource!) - let v = vec![1, 2, 3, 4, 5]; - vec![3; 5] means vec![3, 3, 3, 3, 3] - n-th element => v[n] (indices start from 0, indices are of type usize) - n-th element with out-of-bounds error handling: v.get(n), v.get_mut(n) - add element => v.push(e) - truncate to n elements => v.truncate(n) - element type size should be known at compile time (if not => Box) ** Tuples - let x: (type1, type2) = (val1, val2); - single-element tuple has an extra comma: (0,) - fields are accessed by dot-syntax: x.0, x.1 etc. ** Strings - UTF8-encoded *** &str - string literals, e.g. "Hello World!" - multiline strings (newline + leading spaces included) - '\' at the end of the line => newline + leading whitespace skipped - no indexing => use s.as_bytes() or s.chars() - or use slicing => &s[..] (note: slicing uses byte offsets) *** String - dynamically allocated strings - created with to_string => let mut s = "Hello World!".to_string(); - reference to a String coerces to a &str - concanation with &str => '+' - String => &str cheap - &str => String allocates memory ** Structs - struct Foo { x: type, y: type } - empty struct => struct Bar {} or struct Baz; - object creation: let f = Foo { x: val, y: val } - field access: f.x - copy other fields from another struct g: f = Foo { y: val, .. g } - cannot be partly mutable (mutability is a property of the binding/reference to a struct) - Cell can be used for field-level mutability ** Tuple Structs - struct Foo(x, y); - fields don't have names - field access: f.0, f.1 etc. [or with pattern matching] - when only one element => basically a newtype ** Enumeration / Sum [Union] - enum FooBarBaz { Foo, Bar { a: i32, b: i32 }, Baz(str, str) } - variants can be unit-like => Foo - variants can have named data => Bar { a: i32, b: i32 } - variants can have unnamed data => Baz(str, str) - a variant of an enum is accessed by :: => Foo::Bar { b: 1, a: 3 } - enum constructors can be passed as functions ** Unsized Types - e.g. [T] - only usable via pointers => &[T] - variables/arguments cannot have unsized types - in a struct, only the last field may have an unsized type - in a type annotation, ?Sized means that the type need not be sized ** Wrappers - &T / &mut T => references (see borrowing rules), no cost at runtime - *const T, *mut T => raw pointers (see below), unsafe - Box => "owned" pointer, automatic destructor - Rc => reference-counted pointer (cannot handle cycles), immutable data, not thread-safe - Weak => points to an Rc, but can outlive it - Cell => mutable data for Copy types (cell.set(val), cell.get()) - RefCell => mutable data for any type (cell.borrow(), cell.borrow_mut()), not thread-safe - Arc => atomic reference count, thread-safe version of Rc - Mutex/RwLock => thread-safe mutable data; RwLock is efficient for multiple threads - these can be composed, e.g. Rc>, Rc>> ** Aliases - type NewName = OldName; - not a new type => use tuple structs for that * Generics - generic types or functions, e.g. Foo, bar etc. - for methods => impl Foo { ... } * Control Flow ** If - like in C/C++, but... - no parentheses needed - braces are always needed - it is an expression (can be used as ?:) - when there is no else, it defaults to () - can be combined with pattern matching: if let Some(x) = option { foo(x); } same as match option { Some(x) => foo(x), None => {} } ** Loops - infinite loop => loop { ... } - while => while test { ... } - can be combined with pattern matching: while let pattern = expr { ... } - for loop => for var in expr { ... } where expr is iterable, e.g. begin..end - for loop with counting => for (index, var) in range.enumerate() { ... } - break & continue as in C/C++ but can specify the loop (default is the innermost loop) - 'label: some_loop { ... break 'label; ... } ** Case - match expr { val1 => expr, val2 => { ... }, _ => expr } - 'otherwise' branch: _ - match itself is an expression - useful when working with enums * Macros - basically scheme's syntax-rules (hygienic macros) - macro_rules! name { ... } => creates the macro "name!" - inside there are rules of the form ( ... ) => { ... }; - the pattern contains syntactic matchers, e.g. $var:expr binds an expression to $var - the expression $(...),* means 0 or more expressions of the given format, separated by commas - in the expansion the expression $(...)* handles one iteration - macro expansions can be generated by "rustc --pretty expanded" - macros can be exported to parent modules with #[macro_use(name1, name2)] or #[macro_use] ** Common macros - panic!("we have a problem") - vec![1, 2, 3, 4, 5] / vec![1; 10] - assert!(true) / assert_eq!('a', x) - try!(foo(...)) - unreachable!() - unimplemented!() * Memory Allocation - with the Box type - Box::new(value) - implements Drop * Raw Pointers - *const T / *mut T - creating raw pointers is safe => let raw = &n as *const i32; - dereferencing is not => unsafe { *raw } * Unsafe - unsafe functions => unsafe fn foo(...) { ... } - unsafe block => unsafe { ... } - unsafe traits => unsafe trait Bar { ... } - unsafe implementation => unsafe impl Bar for Baz { ... } * Modules and Crates - tree of modules inside crates (root module is implicit, same name as the crate) - crates are created by the package manager - mod name { ... } [names are conventionally snake_case] - alternatively => mod name; [the module itself is in name.rs or name/mod.rs] - making modules/functions/structs/struct-fields public => pub mod/fn/struct/etc. - loading crates => extern crate crate_name; [crate-name becomes crate_name] - importing modules => use crate::child_mod::grandson_mod; - shorthand => use crate::child_mod::{grandson_mod, granddaughter_mod}, use crate::child_mod::* - importing functions => use crate::child_mod::foo; [not very good practice] - re-exporting functions [normally inside a library] => pub use ... - use-declarations can use "self" and "super" (referring to places in the hierarchy) - renaming imported things => use crate::child_mod as daughter * Testing - #[test] - anything that does not panic! passes - use assert!(p) and assert_eq!(a, b) - when panic!ing is the normal behaviour, add #[should_panic] under #[test] - expensive tests can be ignored by default, by adding #[ignore] under #[test] - tests can also be written in code blocks of the documentation (see Documentation) ** Test Module - #[cfg(test)] above the module declaration => only compiles it when testing - tested functions can be imported => use super::foo; or use super::*; * Package Manager - cargo new name --lib/bin => new library/binary project - cargo build [--release] => builds the project for debug/release - cargo run => runs the project - cargo test => runs unit tests (cargo test -- --ignore => runs also ignored tests) - cargo doc => generates documentation - generates git repo * Error Handling - Option and Result types [~ Maybe and Either in Haskell] - unwrap(): panic for None/errors - expect(msg): panic for None/errors with a message - try! macro: unwraps or returns (using the From trait to convert the error) - Error trait for defining our own error types ** Combinators - map(fn): ~ fmap - unwrap_or(val): unwrap but uses val for None/errors - and_then(fn): like map, but the result of fn is already an Option/Result, so this flattens it - ok_or(msg): converts an Option to a Result, giving the error message msg - map_err(fn): maps a function on the error part of a Result * Concurrency - Send trait => can be transferred between threads - Sync trait => can be used from multiple threads concurrently - let handle = std::thread::spawn(closure); * FFI - #[link(name = "library")] - extern { fn foo(var: type) -> type; } - calling in unsafe blocks - types from libc => extern crate libc; [e.g.: use libc::size_t;] - libc::c_void type for opaque structs