source: https://github.com/PacktPublishing/Hands-On-Data-Structures-and-Algorithms-with-Rust
- plugin https://github.com/rust-lang/rls-vscode
- Rust Language Server (RLS) https://github.com/rust-lang/rls-vscode - installed via rustup https://rustup.rs/
- Debugging support - LLDB frontend plugin https://github.com/vadimcn/vscode-lldb
- trait mixin + test refresher
struct
,impl
- tests:
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "xxx")]
fn do_something() {
assert!(...);
}
}
Option<T>
,Result<T, E>
-if let Some
,match .. Some .. None
,match .. Ok .. Err
vec![]
macro:
#[macro_export]
macro_rules!
vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$( temp_vec.push($x); )*
temp_vec
}
};
}
#[derive(Clone, Debug)]
-"{:?}"
Box
,Rc
&x
,mut
,clone
fn f<'a>(mut pass_through: MyStruct<'a>, x: &'a Vec<u32>) -> MyStruct<'a> {
- multiple owners:
use std::rc::Rc;
#[derive(Debug)]
struct FileName {
name: Rc<String>,
ext: Rc<String>
}
fn ref_counter() {
let name = Rc::new(String::from("main"));
let ext = Rc::new(String::from("rs")));
for _ in 0..3 {
println!("{;?}", FileName {
name: name.clone(),
ext: ext.clone()
});
}
}
RefCell
interior mutability - maintains single ownership of a value but allows mutable borrowing checked at runtime. Instead of compiler errors, violating rules of borrowing will lead to a runtimepanic!
, crashing the program. used in combination withRc
to provide a value to multiple owners with mutability.borrow_mut()
- mutable reference only lives as long as the assignment
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Clone)]
struct Node {
value: String,
next: Link,
prev: Link,
}
type Link = Option<Rc<RefCell<Node>>>;
pub fn append(&mut self, value: String) {
let new = Rc::new(RefCell::new(Node::new(value)));
match self.tail.take() {
Some(old) => {
old.borrow_mut().next = Some(new.clone());
new.borrow_mut().prev = Some(old);
}
None => self.head = Some(new.clone()),
};
}
use std::thread;
use std::sync::{Mutex, Arc};
use std::sync::mpsc::{channel, Sender, Receiver};
use std::rc::Rc;
use std::cell::RefCell;
use std::collections::HashMap;
...
fn main() {
threading();
shared_state();
channels();
ref_counter();
}
- marker traits:
Send
: type is safe to send (move
) from one thread to the otherSync
: type can be shared across threads withoutMutex
- doc tests
- cargo config
- cargo workspace
- cargo flags:
--version
--list
List installed commands--explain <CODE>
Runrustc --explain CODE
--verbose
(-vv
very verbosebuild.rs
output)--quiet
--color <WHEN>
--frozen
RequireCargo.lock
and cache are up to date--locked
RequireCargo.lock
is up to date-Z <FLAG>...
Unstable (nightly-only) flags to Cargo
- cargo commands:
build
Compilecheck
Analyze, but don't build object filesclean
Removetarget
directorydoc
Build project + its dependencies' docsnew
Create a new cargo projectinit
Create a new cargo project in an existing directoryrun
Build and executesrc/main.rs
test
Run testsbench
Run benchmarks -#[bench]
is nightly unstable in RUst 2021update
Update dependencies listed inCargo.lock
search
Search registry for cratespublish
Package as *.crate file and upload project to https://crates.ioyank
prohibit updates to a particular published versioninstall
Install a Rust binaryuninstall
Uninstall a Rust binary
- .cargo/config file: https://doc.rust-lang.org/cargo/reference/config.html
paths
- local repos[cargo-new]
- commandline args[registry]
- remote repos[http]
- proxy address + port,cainfo
,timeout
[build]
- number ofjobs
, binaries (eg rustc, rustdoc),incremental
compilation
- Cargo.lock https://doc.rust-lang.org/cargo/faq.html provide deterministic builds of dependency versions. architecture specific. frozen unless you run
cargo update
- Cargo.toml manifest
package.metadata
[target.$triple]
- eg
[target.wasm32-unknown-unknown]
for Wasm target via LLVM backend - has to be installed:rustup target add wasm32-unknown-unknown
. wasm-bindgen https://github.com/rustwasm/wasm-bindgen - specify
linker
,ar
(archiver),runner
, compilerrustflags
- compiled to target///
- profiles:
[profile.dev]
->cargo build
[profile.release]
->cargo build --release
[profile.test]
-> ,cargo test
[profile.bench]
->cargo bench
- defaults:
- eg
[profile.release]
opt-level = 3
debug = false
rpath = false
lto = false
debug-assertions = false
codegen-units = 16
panic = 'unwind'
incremental = false
overflow-checks = false
[dependencies]
[dependencies]
# import in Rust with "use x::*"
x = "*"
# (~): Only semver patch increases allowed
# (^): only semver minor increases allowed
# (*): semver major increases allowed. Note: isn't possible to cargo publish a crate with wildcard dependencies
y = { version = "0.5", optional = true }
v = { git = "https://github.com/vvv", branch = "0.4" }
# import in Rust with "use z::*"
[dependencies.z]
version = "0.5"
features = ["www"] # only install subcomponents
[dev-dependencies]
[build-dependencies]
links
- native libs to be linked dynamicallybuild
- path or name to a build script https://doc.rust-lang.org/cargo/reference/build-scripts.html any output for cargo is passed viacargo:key=value
format- place each subproject in a subdirectory and create a
workspace
via Cargo.toml :
[workspace]
members = [ "core", "web", "data"]
[build-dependencies] # no other dependencies allowed
rustup component add clippy-preview
https://github.com/rust-lang/cargo/wiki/Third-party-cargo-subcommands- linking https://doc.rust-lang.org/reference/linkage.html
--crate-format=
rlib
,dylib
,staticlib
,library
, orbin
- Static: Via
rlib
format (default native package format). for static libs, all dependencies have to be of rlib type - Dynamic: Via shared libraries (
.so
or.dll
)rustc -C prefer-dynamic
- FFI
extern "C" {
fn imported_function() -> i32; # must be bound via the linker. call must be wrapped in an `unsafe` section
}
#[no_mangle]
pub extern "C" fn exported_function() -> i32 { # no_mangle -> can be found using its name
42
}
- stack - allows for zero overhead structures
- heap - Types that don't have predictable sizes. objects wrapped into
Rc
,Cell
,RefCell
, orBox
instances - store fixed-size reference
&str
toString
heap-allocated value, along with metadata for length in bytes. Similar to pointers, a fixed-size view into a previously-unsized value. - mem::size_of
- generics - equivelent:
fn f<T: MyTrait>(t: T) {}
fn f <T>(t: T) where T: MyTrait {}
fn f(t: impl MyTrait) {}
fn f(x: T) where T: Clone + Debug + MyTrait {}
- Generic type parameters are
Sized
by defaultfn f <T: Sized>(t: T) {}
- marker trait for mostly stack-allocated values is the default, can be relaxed to?Sized
- memory leaks are still possible in Rust via
Rc
Box<dyn TheTrait>
put a reference to a trait inside of a struct. dynamic dispatch: location is only known at runtime - vtable lookup is slower. use concrete types instead of traits to avoid multiple dereference operationsCopy
via assignment for sized stack values is an implicit, bitwise copy. implement or derive#derive[Clone]
for unsized heap refsclone()
std::borrow::Cow
lazily clones a contained reference whenever mutation or ownership is requested https://doc.rust-lang.org/std/borrow/enum.Cow.html- Rust Persistent Data Structures (RPDS) https://crates.io/crates/rpds utilize copy-on-write with versioning to capture state changes. Persistent data structures: keep original data immutable and store versioned change sets.
- no
unsafe {}
-> code gets riddled withRefCells
andborrow()
to create data structures that the borrow checker passes - Rust
LinkedList
uses much unsafe code internally.PhantomData<T>
zero-size type - informs compiler about generic types (drop behavior, sizes) - recursive nested
next
pointers can exceed the stack -> stack overflows with autogeneratedDrop
,Debug
-> implementDrop
traitdrop()
and dispose of list elements iteratively. - bencher
- tests
- single linked list
type Link = Option<Rc<RefCell<Node>>>
String.to_owned
borrow
/borrow_mut
Rc::try_unwrap(x) .ok() .expect("xxx") .into_inner() .value
- LinkedList
Option.take()
https://doc.rust-lang.org/std/option/enum.Option.html#method.takemem::replace()
https://doc.rust-lang.org/stable/std/mem/fn.replace.html
- double linked list
- implementing
iter
https://doc.rust-lang.org/std/iter/index.html#implementing-iteratorIterator
,IntoIterator
,DoubleEndedIterator
- implementing
- skip list
O(log n)
probabilistic search on a linked list
#[derive(Clone)]
struct Node {
next: Vec<Link>,
pub offset: u64,
pub value: String,
}
#[derive(Clone)]
pub struct MySkipList {
head: Link,
tails: Vec<Link>,
max_level: usize,
pub length: u64,
}
fn get_level(&self) -> usize {
let mut n = 0;
// bool = p(true) = 0.5
while rand::random::<bool>() && n < self.max_level {
n += 1;
}
n
}
- dynamic array
Box<[Node]>
,vec!
into_boxed_slice
,clone_from_slice
- tests
- binary search tree
- binary_search_tree_walk_in_order
- binary_search_tree_find
- red black tree
- red_black_tree_add
- red_black_tree_walk_in_order
- red_black_tree_find
- binary heap
- binary_heap_add
- binary_heap_pop
- trie
- trie_add
- trie_walk_in_order
- trie_find
- btree
- btree_add
- btree_find
- btree_walk_in_order
- graphs
- graph_insert_edges
- graph_neighbors
- graph_find_shortest_path
- binary search tree
- binary search tree
mem::replace
- pass callback and build a vector by walking tree:
walk(&self, callback: impl Fn(&T) -> ()) {
self.walk_in_order(&self.root, &callback);
}
fn walk_in_order(&self, node: &Tree, callback: &impl Fn(&T) -> ()) {
if let Some(n) = node {
self.walk_in_order(&n.left, callback);
callback(&n.dev);
self.walk_in_order(&n.right, callback);
}
}
let items: RefCell<Vec<T>> = RefCell::new(vec![]);
tree.walk(|n|items.borrow_mut().push(n.clone()));
- test with rand
use rand::thread_rng;
use rand::seq::SliceRandom;
use rand::Rng;
...
let mut items: Vec<T> = (0..len).map(new_item_with_id).collect();
// let mut rng = thread_rng();
// rng.shuffle(&mut items);
items.shuffle(&mut thread_rng());
for item in items.iter() {
tree.add(item.clone());
}
assert_eq!(tree.length, len);
let v: RefCell<Vec<T>> = RefCell::new(vec![]);
tree.walk(|n| v.borrow_mut().push(n.clone()));
let mut items = items;
// sort in descending order:
items.sort_by(|a, b| b.numerical_id.cmp(&a.numerical_id));
assert_eq!(v.into_inner(), items)
- random shuffle:
use rand::thread_rng;
use rand::seq::SliceRandom;
use rand::Rng;
let mut tree = btree::DeviceDatabase::new_empty(3);
let mut items: Vec<IoTDevice> = (0..len).map(new_device_with_id).collect();
items.shuffle(&mut thread_rng());
for item in items.iter() {
tree.add(item.clone());
}
- red-black tree
- heap
Vec<T>.swap_remove()
- remove 1st element of by replacing it with last element
- trie
- random gen_range:
use rand::thread_rng;
use rand::Rng;
let mut trie = trie::BestDeviceRegistry::new_empty();
let len = 10;
let mut rng = thread_rng();
for i in 0..len {
trie.add(new_device_with_id_path(
i,
format!("factory{}/machineA/{}", rng.gen_range(0..len), i),
));
}