Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rust in a nutshell

Rust in a nutshell

A short introduction in Rust programming language.

Links used in the presentation:
Slide #2
- https://msrc-blog.microsoft.com/2019/07/16/a-proactive-approach-to-more-secure-code/

Slide #6
- https://blog.rust-lang.org/2018/03/12/roadmap.html
- http://arewegameyet.com/

Slide #7
- https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5dbea5eb6f9763a675e40b0e33d4d650

Slide #8
- https://doc.rust-lang.org/error-index.html#E0382

Slide #9
- https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=7a8145de5f4b6739b3bfd8e0122674df
- https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=747b47fead8ff62d7a5891c2fe412095

Slide #12
- https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=cd4b67a506e15a67c3bfbbc531a9fe9c

Slide #17
- https://github.com/rust-lang/rustup#installation

Slide #28
- https://doc.rust-lang.org/cargo/guide/index.html

Slide #32
- https://users.rust-lang.org/
- https://internals.rust-lang.org/
- https://www.reddit.com/r/rust/
- https://discordapp.com/invite/rust-lang

Slide #35
- https://doc.rust-lang.org/book/ch03-02-data-types.html

Slide #36
- https://doc.rust-lang.org/book/ch03-03-how-functions-work.html

Slide #37
- https://doc.rust-lang.org/rust-by-example/generics/where.html
- https://github.com/tokio-rs/tokio/blob/8546ff826db8dba1e39b4119ad909fb6cab2492a/tokio/src/runtime/thread_pool/slice.rs#L58-L65

Slide #38
- https://doc.rust-lang.org/1.1.0/book/for-loops.html
- https://doc.rust-lang.org/1.1.0/book/while-loops.html

Slide #39
- https://doc.rust-lang.org/std/iter/trait.Iterator.html
- https://doc.rust-lang.org/1.1.0/book/iterators.html

Slide #40
- https://docs.serde.rs/serde_json/value/enum.Value.html

Slide #41
- https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=0d556df585028c542c2c4f7cb9baa6cc

Slides #42 - #43
- https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b45e0fe6a14519f408ee83216ad5e4d4

Slides #44 - #45
- https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=16fb9dcb923773904eb4dc2526ef6a96

Slide #46
- https://doc.rust-lang.org/1.29.0/book/first-edition/trait-objects.html#static-dispatch
- https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=24e6a09a195eec50560f5e9281ba772a

Slide #47
- https://doc.rust-lang.org/1.29.0/book/first-edition/trait-objects.html#dynamic-dispatch
- https://github.com/Relrin/quickproj/blob/6706866943506af9f0f25d2d2af3157fcefb6602/src/installers/traits.rs#L1-L7
- https://github.com/Relrin/quickproj/blob/6706866943506af9f0f25d2d2af3157fcefb6602/src/installers/git.rs#L58-L114

Slide #48
- https://doc.rust-lang.org/1.29.0/book/first-edition/trait-objects.html#dynamic-dispatch
- https://github.com/Relrin/quickproj/blob/6706866943506af9f0f25d2d2af3157fcefb6602/src/client.rs#L183-L188
- https://github.com/Relrin/quickproj/blob/6706866943506af9f0f25d2d2af3157fcefb6602/src/client.rs#L170

Slide #51
- https://doc.rust-lang.org/std/option/enum.Option.html
- https://doc.rust-lang.org/std/result/enum.Result.html

Slide #52
- https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=dc1496a31e40b60f4ea6a9abfc7ea8ad

Slide #53
- https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=01adab30395cbca64493a2e7ea1b191a

Slide #55
- https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d5ffeef8d9354c57dc79f9d8708f6e91

Slide #56
- https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b69d97c79076a0d8cba303f228b7412c

Slide #57
- https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=61bf9d1e125f9c4e9d625f18da87081d

Slide #58
- https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e336be396b1accf4ed109cc80c9fa93a

Slide #61
- https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f7838d1d44c34d53c5e6ba4396f8520a

Slide #63
- https://github.com/rust-lang/rls
- https://intellij-rust.github.io/

Slide #64
- https://doc.rust-lang.org/book/index.html
- https://doc.rust-lang.org/rust-by-example/
- https://stevedonovan.github.io/rust-gentle-intro/

Valeryi Savich

December 05, 2019
Tweet

More Decks by Valeryi Savich

Other Decks in Programming

Transcript

  1. General information Announced: July 7, 2010 First pre-alpha release: 0.1

    / January 2012 First stable release: 1.0.0 / May 15, 2015 Latest stable release: 1.39.0 / November 7, 2019 Paradigms: concurrent, functional, generic, imperative, structured Designed to be a programming language that combines memory safety with high performance.
  2. Good in many areas ◦ Network services. Rust’s reliability and

    low footprint make it an excellent match for network services and infrastructure, especially at high scale. ◦ Command-line apps (CLI). Rust’s portability, reliability, ergonomics, and ability to produce static binaries come together to great effect for writing CLI apps. ◦ WebAssembly. The “wasm” web standard allows shipping native-like binaries to all major browsers. Rust is extremely well positioned to target this domain, and provides a reasonable on-ramp for programmers coming from JS. ◦ Embedded devices. Rust has the potential to make programming resource-constrained devices much more productive. ◦ Game development. Its emphasis on low-level memory safe programming promise a better development process, less debugging time, and better end result. While the ecosystem is still very young, you can find enough libraries and game engines to sink your teeth into doing some slightly experimental gamedev. Sources: Rust’s 2018 roadmap and Are we game yet?
  3. fn foo(line: String) { println!("{}", line); } fn main() {

    let x = String::from(“Hello, world!”); foo(x); foo(x) } *Link to the example Smart compiler with borrow checker Compiling playground v0.0.1 (/playground) error[E0382]: use of moved value: `x` --> src/main.rs:8:9 | 6 | let x = String::from(“Hello, world!”); | - move occurs because `x` has type `std::string::String`, which does not implement the `Copy` trait 7 | foo(x); | - value moved here 8 | foo(x) | ^ value used here after move error: aborting due to previous error For more information about this error, try `rustc --explain E0382`. error: could not compile `playground`. To learn more, run the command again with --verbose. Won’t compile
  4. *https://doc.rust-lang.org/error-index.html#E0382 Compiling playground v0.0.1 (/playground) error[E0382]: use of moved value:

    `x` --> src/main.rs:8:9 | 6 | let x = String::from(“Hello, world!”); | - move occurs because `x` has type `std::string::String`, which does not implement the `Copy` trait 7 | foo(x); | - value moved here 8 | foo(x) | ^ value used here after move error: aborting due to previous error For more information about this error, try `rustc --explain E0382`. error: could not compile `playground`. To learn more, run the command again with --verbose. Friendly errors
  5. fn get_directory_objects(directory: &PathBuf) -> Vec<PathBuf> { WalkDir::new(directory.clone()) .into_iter() .filter_map(|entry| entry.ok())

    .filter(|entry| entry.path() != directory) .map(|entry| entry.clone().into_path()) .collect() } Expressive and efficient *Links to the examples: first and second fn get_directory_objects(directory: &PathBuf) -> Vec<PathBuf> { let mut objects = Vec::new(); for dir_entry in WalkDir::new(directory.clone()).into_iter() { let object = match dir_entry { Ok(value) => value, _ => continue, }; if object.path() != directory { objects.push(object.into_path()) } } objects }
  6. Memory safety - Rust ensures that there is exactly one

    binding to any given resource - Acceptable either only one mutable reference OR many immutable references - No null, only Option + compiler checks - Ownership rules apply across multiple threads - Checks for the access on collection’s boundaries (e.g. arrays/vectors)
  7. Fearless concurrency use std::thread; // A dangling pointer somewhere here...

    fn main() { let value = 1; thread::spawn(|| { println!("value is {}", value); }); } *Link to the example Compiling playground v0.0.1 (/playground) error[E0373]: closure may outlive the current function, but it borrows `value`, which is owned by the current function --> src/main.rs:6:19 | 6 | thread::spawn(|| { println!("value is {}", value); }); | ^^ ----- `value` is borrowed here | | | may outlive borrowed value `value` | note: function requires argument type to outlive `'static` --> src/main.rs:6:5 | 6 | thread::spawn(|| { println!("value is {}", value); }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: to force the closure to take ownership of `value` (and any other referenced variables), use the `move` keyword | 6 | thread::spawn(move || { println!("value is {}", value); }); | ^^^^^^^ error: aborting due to previous error For more information about this error, try `rustc --explain E0373`. error: could not compile `playground`.
  8. OCaml ADT, Pattern matching, Type inference C++ RAII, Smart pointers,

    Move semantics Haskell Typeclasses, Type families Schema Hygienic macros C# Attributes Erlang Message passing, Thread failures Ruby Closure syntax Influences
  9. curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh or for

    Windows OS* https://win.rustup.rs/ *https://github.com/rust-lang/rustup#installation
  10. *https://doc.rust-lang.org/cargo/guide/index.html => cargo new --bin hello-rustlang Created binary (application) `hello-rustlang`

    package => cd hello-rustlang/ => cargo run Compiling hello-rustlang v0.1.0 (/Users/valeryisavich/code/hello-rustlang) Finished dev [unoptimized + debuginfo] target(s) in 0.92s Running `target/debug/hello-rustlang` Hello, world!
  11. Where to talk Rustlang forums - Users forum: https://users.rust-lang.org -

    Internals forum: https://internals.rust-lang.org/ Reddit - https://www.reddit.com/r/rust/ Twitter - @rustlang - #rustlang Discord - https://discordapp.com/invite/rust-lang
  12. Primitive types Type Rust keyword Boolean bool Char char String

    String Integer 8-bit i8 Integer 16-bit i16 Integer 32-bit i32 Unsigned integer 8-bit u8 Unsigned integer 16-bit u16 Unsigned integer 32-bit u32 Float 32-bit f32 Float 64-bit f64 - Rust tries to be minimalistic with declaring any kind of types - Also exist 64/128-bit versions of signed/unsigned integers out-of-the-box - Be careful with overflows, because the program will panic by default with typical math operations. But can be handled with checked_*, overflow_*, wrapping_*, overflowing_* methods depends on your case.
  13. Primitive types *More info: Rust data types What about arrays

    with slices? let example = (1, “hello"); // General usage let result: (i32, &str) = (1, "hello"); // With the declared types // Extracting values from the tuple by index let tuple = (1, 2, 3); let first = tuple.0; let second = tuple.1; let third = tuple.2; And tuples: // Arrays let arr = [0, 1, 2, 3, 4]; // Regular declaration let generic_arr = [i32; 3]; // Generic, each element will be initialized to 0 // Slices let middle = &arr[1..4]; // A slice of arr: just the elements 1, 2, and 3 let complete = &arr[..]; // A slice containing all of the elements in arr
  14. Functions fn fibonacci(n: u32) -> u32 { if n ==

    0 { return 0 } else if n == 1 { return 1; } fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2) } early returns argument type function name result type argument - Looks similar as in other existing programming languages returned result - By default functions are private. Use the pub keyword to make it public - The last expression used as the returned result (implicit return) - Functions without specified result type returns an empty tuple *More info: Functions
  15. Functions *More info: Where clause in functions and source code

    - For generic functions developers can set restrictions on arbitrary types pub fn spawn_typed<F>(&self, future: F) -> JoinHandle<F::Output> where F: Future + Send + 'static, F::Output: Send + 'static, { let (task, handle) = task::joinable(future); self.schedule(task); handle } generic type restrictions
  16. Iterating by collections - Rust doesn’t have “C-style” for loop

    on purpose - for loop construction is similar to Python / Ruby - The continue / break instructions also can be used in loops let vector = vec![1,2,3,4,5]; for value in vector { println!("{}", value); // value: i32 } - Use while / loop keywords for implement an infinite loop let mut x = 5; let mut done = false; while !done { x += 3; println!("{}", x); if x % 5 == 0 { done = true; } } let mut x = 5; loop { x += 3; println!("{}", x); if x % 5 == 0 { break; } } *More info: for keyword, while and loop keywords
  17. Iterating by collections - For doing iterations by collections Rust

    relies on the Iterator trait - There’s a lot of collections out-of-the-box, for example: - Vector - HashMap - BTreeSet - Any of built-in collection type implements additional traits for operating on an iterator (=iterator adapters) and consumers (=producer of the new set of values) *More info: Iterator trait, Iterators in rust (1..1000) .filter(|&x| x % 2 == 0) .filter(|&x| x % 3 == 0) .take(5) .collect::<Vec<i32>>(); range declaration consumer iterator adapters
  18. ADT & pattern matching *More info: serde_json::value::Value enum Value {

    Null, Bool(bool), Number(f64), String(String), Array(Box<Vec<Value>>), Object(HashMap<String, Value>), } - Inspired by OCaml - Rust provides extracting values from enums in controlled fashion - Compiler always checks that developer covered all cases
  19. ADT & pattern matching *Link to the example use serde_json::{json,

    Value}; fn main() { let example = json!({ "key": "value" }); match example { Value::Null => println!("Null"), Value::Bool(val) => println!("Boolean: {}", val), Value::Number(val) => println!("Number: {}", val), Value::Array(_) | Value::Object(_) => { println!("It's a collection"); }, _ => println!("Other"), }; }
  20. Structures *Link to the example - Defining like structures in

    C - All fields are private by default - Struct and its fields can be aligned manually - No data inheritance: use composition for constructing objects struct User { username: String, email: String, sign_in_count: u64, active: bool, } // ... let user = User { username: String::from("User"), email: String::from("[email protected]"), sign_in_count: 5, active: true, };
  21. Structures *Link to the example impl User { pub fn

    new( username: &String, email: &String, sign_in_count: u64, active: bool ) -> Self { User { username: username.to_owned(), email: email.to_owned(), sign_in_count: sign_in_count, active: active, } } } constructor call // ... let user = User::new( &String::from("User"), &String::from("[email protected]"), 5, true, ); string copy - It’s quite common practice to write constructors for structs - Constructor can have any name that developer would like to use - Usage the new(…) method is the rule of thumb
  22. Structures *Link to the example - Alternatively we can implement

    construction objects via the builder pattern - Will be a good choice if struct has a lot of fields and required granular control - Usage the Default::default() method is the rule of thumb struct User { username: Option<String>, email: Option<String>, sign_in_count: u64, active: bool, } impl User { pub fn with_username(mut self, username: &String) -> Self { self.username = Some(username.to_owned()); self } pub fn with_email(mut self, email: &String) -> Self { self.email = Some(email.to_owned()); self } // ... } Wrapped in Option<T>
  23. Structures *Link to the example impl Default for User {

    fn default() -> Self { User { username: None, email: None, sign_in_count: 0, active: true, } } } fn main() { let user = User::default() .with_username(&String::from("User")) .with_email(&String::from("[email protected]")); println!("{:?}", user); } No data by default
  24. Traits *More info: Static dispatch and example - Consider this

    abstraction as an interface for types - Can be inherited by other trait. But it also means that the type must satisfy the parent trait requirements - Can be implemented for any data type - Can provide default method definitions trait Foo { fn method(&self) -> String; } impl Foo for u8 { fn method(&self) -> String { format!("u8: {}", *self) } } impl Foo for String { fn method(&self) -> String { format!("string: {}", *self) } } fn do_something<T: Foo>(x: T) -> String { x.method() } fn main() { let x = 5u8; let y = "Hello".to_string(); println!("{}", do_something(x)); println!("{}", do_something(y)); } static dispatch (monomorphism)
  25. Traits *More info: Dynamic dispatch; Installer trait, implementation pub trait

    Installer { fn new() -> Self where Self: Sized; fn get_template_name(&self, path: &String) -> Result<String, Error>; fn install(&self, path: &String, template_name: &String) -> Result<(), Error>; } pub struct GitInstaller; impl Installer for GitInstaller { fn new() -> Self { GitInstaller {} } fn get_template_name(&self, url: &String) -> Result<String, Error> { // ... } fn install(&self, url: &String, template_name: &String) -> Result<(), Error> { // ... } } - For dynamic dispatch is quite similar approach - Dynamic dispatch uses vtables (like in C++)
  26. Traits *More info: Dynamic dispatch; allocating instance, method call fn

    get_installer_from_enum(&self, value: &InstallerTypeEnum) -> Box<dyn Installer> { match value { InstallerTypeEnum::Git => Box::new(GitInstaller::new()), InstallerTypeEnum::Local => Box::new(LocalInstaller::new()), } } fn install_template( &self, installer_type: &InstallerTypeEnum, path: &String, template_name: &Option<String>, ) -> Result<(), Error> { let worker = self.get_installer_from_enum(installer_type); // ... worker.install(path, &used_template_name) } allocation in heap dynamic dispatch
  27. General information - Rust doesn’t have exceptions for errors handling

    - But using Option<T> and Result<T, E> enums instead - Errors are explicitly propagated - In general, consider error handling in Rust as the case analysis to determine whether a operation was successful or not enum Option<T> { Some(T), None, } enum Result<T, E> { Ok(T), Err(E), } - Reflects a possibility of data absence - Semantically equivalent to the type Option<T> = Result<T, ()> - Reflects a possibility of error
  28. General information - Be quite careful when using .unwrap() because

    it throws panics - Throwing panics means that you have a BUG in code (or in the dependency) and can be caused by: •Usage of the .unwrap() calls in code •Explicit usage of the panic! / unreachable! macro •Unsafe blocks with unhandled errors or invalid pointers Function name Description unwrap() Moves the value v out of the Option<T> if it is Some(v) unwrap_or(val) Returns the contained value or a default unwrap_or_default() Returns the contained value or a default unwrap_or_else(func) Returns the contained value or computes it from a closure. Links: Option<T> and Result<T, E> The Option<T> and Result<T, E> enums are implement a lot various traits so you could choose the best call for the certain case. For example:
  29. A simple example *Link to the example use std::fs::File; use

    std::io::{Read, Write}; use std::path::Path; use tempfile::NamedTempFile; fn file_double(file_path: &Path) -> i32 { let mut file = File::open(file_path).unwrap(); // error 1 let mut contents = String::new(); file.read_to_string(&mut contents).unwrap(); // error 2 let n: i32 = contents.trim().parse().unwrap(); // error 3 2 * n } fn main() { let mut file = NamedTempFile::new().unwrap(); let text = "10"; file.write_all(text.as_bytes()).unwrap(); let file_path = file.path(); let doubled = file_double(&file_path); println!("{}", doubled); }
  30. Iteration #1 *Link to the example fn file_double(file_path: &Path) ->

    Result<i32, String> { let mut file = match File::open(file_path) { Ok(file) => file, Err(err) => return Err(err.to_string()), }; let mut contents = String::new(); if let Err(err) = file.read_to_string(&mut contents) { return Err(err.to_string()); } let n: i32 = match contents.trim().parse() { Ok(n) => n, Err(err) => return Err(err.to_string()), }; Ok(2 * n) } fn main() { // … let file_path = file.path(); // Path::new("./file.txt") for IoError match file_double(&file_path) { Ok(value) => println!("Result: {}", value), Err(err) => println!("Error: {}", err), } }
  31. But we can make it much better… - Let’s implement

    a custom Error enum for handling potential errors - Convert the incoming errors into our types or store them in enums - Replace match expressions onto the try! macro - Use crates that eliminates boilerplate code for handling errors, e.g. • quick-error • error-chain • failure The try! macro abstracts case analysis like combinators, but unlike combinators, it also abstracts control flow. Namely, it can abstract the early return pattern. - (c) The Rust programming language book macro_rules! try { ($e:expr) => (match $e { Ok(val) => val, Err(err) => return Err(::std::convert::From::from(err)), }); }
  32. Iteration #2 *Link to the example use std::fs::File; use std::io::{Read,

    Write}; use std::path::Path; use tempfile::NamedTempFile; fn file_double(file_path: &Path) -> Result<i32, String> { let mut file = try!(File::open(file_path).map_err(|e| e.to_string())); let mut contents = String::new(); try!(file.read_to_string(&mut contents).map_err(|e| e.to_string())); let n = try!(contents.trim().parse::<i32>().map_err(|e| e.to_string())); Ok(2 * n) } fn main() { let mut file = NamedTempFile::new().unwrap(); let text = "10"; // "not a value" for ParseError file.write_all(text.as_bytes()).unwrap(); let file_path = file.path(); // Path::new("./file.txt") for IoError match file_double(&file_path) { Ok(value) => println!("Result: {}", value), Err(err) => println!("Error: {}", err), } }
  33. Iteration #3 *Link to the example use std::fs::File; use std::io::{self,

    Read, Write}; use std::path::Path; use std::num; use tempfile::NamedTempFile; // We derive `Debug` because all types should probably derive `Debug`. // This gives us a reasonable human-readable description of `CliError` values. #[derive(Debug)] enum CliError { Io(io::Error), Parse(num::ParseIntError), } fn file_double(file_path: &Path) -> Result<i32, CliError> { let mut file = try!(File::open(file_path).map_err(CliError::Io)); let mut contents = String::new(); try!(file.read_to_string(&mut contents).map_err(CliError::Io)); let n: i32 = try!(contents.trim().parse().map_err(CliError::Parse)); Ok(2 * n) }
  34. Iteration #4 *Link to the example #[derive(Debug)] enum CliError {

    Io(io::Error), Parse(num::ParseIntError), } impl From<io::Error> for CliError { fn from(err: io::Error) -> CliError { CliError::Io(err) } } impl From<num::ParseIntError> for CliError { fn from(err: num::ParseIntError) -> CliError { CliError::Parse(err) } } fn file_double(file_path: &Path) -> Result<i32, CliError> { let mut file = try!(File::open(file_path)); let mut contents = String::new(); try!(file.read_to_string(&mut contents)); let n: i32 = try!(contents.trim().parse()); Ok(2 * n) }
  35. Final iteration *Link to the example use quick_error::quick_error; quick_error! {

    #[derive(Debug)] pub enum CliError { Io(err: io::Error) { from() description("io error") display("I/O error: {}", err) cause(err) } Parse(err: num::ParseIntError) { from() description("parse error") display("Parse error: {}", err) cause(err) } } } fn file_double(file_path: &Path) -> Result<i32, CliError> { let mut file = File::open(file_path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; let n: i32 = contents.trim().parse()?; Ok(2 * n) }
  36. General information - For the assertions Rust provides two macro:

    •assert_eq!(left, right) •assert_ne!(left, right) - The module with tests must be marked with #[cfg(test)] attribute - All used structs, functions must be imported in the test module - Each test inside of test module can be marked with the following attributes: - #[test] - #[should_panic] - #[ignore] - For running tests use the cargo test command
  37. fn sum(a: u32, b: u32) -> u32 { if b

    == 3 { panic!(); } a + b } fn main() { let result = sum(2, 2); println!("Result: {}", result); } *Link to the example #[cfg(test)] mod tests { use crate::sum; #[test] fn correct_test() { assert_eq!(sum(2, 2), 4); } #[test] fn should_fail() { assert_eq!(sum(2, 3), 4); } #[test] #[should_panic] fn test_func_with_panic() { assert_eq!(sum(2, 3), 4); } } An example
  38. IDEs for developing - Rust language server (RLS) •Requires rustup

    toolchain for installation •Works with the most popular text editors, e.g.: •VSCode •Vim •Emacs - IntelliJ Rust •Installing as the plugin for IDE •Uses its own language analysis engine •Works with JetBrains products (IntelliJ, CLion, PyCharm, etc) •The best dev experience with CLion •Integration with debuggers (gdb, lldb) •CPU profiler •Valgrind memcheck
  39. Books - The Rust programming language (free) - Rust by

    Example (free) - A Gentle Introduction To Rust (free)
  40. Q&A