- Memory safe. I like to think of it as being given a dull-rusty blade rather than a sharp finely crafted japanese katana/wakizashi. Now go run about and see which cuts you faster!
- Support for Futures (aka Promises in JS parlance).
- Threadsafe concurrency features:
Arc
vs.Mutex<T>
etc. - Generics and Zero-cost abstractions
- Runs on anything with a CPU. Even Android.
- Supports full WASM (WebAssembly). Here's a demo.
- Fabulous tooling via Cargo.
- Much more covered by this great talk by Jon Gjengset (@jonhoo) https://www.youtube.com/watch?v=DnT-LUQgc7s
Due to the way borrowing works, this is possible
// Easily convert to &str, due to dereferencing
let s = String::new();
// Taking a slice is also valid but more verbose
let t: &str = &s[..];
// This is cleaner.
let u: &str = &s;
let s = String::new();
let t = String::new();
let combined = s + &t;
Here's an example to look at Unicode encoding of strings. Rust by default encodes Strings (and string slices) as UTF-8.
let s = String::from("Hey,");
let t = String::from(" oh hai");
let combined = s + &t;
for char in combined.chars() {
let value: String = char.escape_unicode().collect();
println!("Char {} to hex Unicode escape {}", char, value);
}
// Output:
//
// Char H to hex Unicode escape \u{48}
// Char e to hex Unicode escape \u{65}
// Char y to hex Unicode escape \u{79}
// Char , to hex Unicode escape \u{2c}
// Char to hex Unicode escape \u{20}
// Char o to hex Unicode escape \u{6f}
// Char h to hex Unicode escape \u{68}
// Char to hex Unicode escape \u{20}
// Char h to hex Unicode escape \u{68}
// Char a to hex Unicode escape \u{61}
// Char i to hex Unicode escape \u{69}
let char = "\u{63}".chars().next();
if let Some(i) = char {
let mut b = [0; 1];
let value = i.encode_utf8(&mut b);
println!("Char {}", value);
}
// Output:
//
// Char c
// Similarly we can see that a String containing this particular emoji has a capacity, which is the size of the content allocated on the heap, as the Unicode scalar value takes 4-bytes of storage.
let a = String::from("🐶");
println!("Output {}", a.capacity());
// Output 4
let b = String::from("a");
println!("Output {}", b.capacity());
// Output 1
struct Handler;
trait EventHandler {
fn foo(&self) -> &str;
}
impl EventHandler for Handler {
fn foo(&self) -> &'static str {
let message = "hello";
println!("Handler: {}", message);
message
}
}
let myHandler = Handler;
myHandler.foo();
- Example of implementing the Debug trait on a struct https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=403191d1ff880b15ae693a82403a4ab8
use std::fmt;
use std::panic;
#[derive(Debug)]
#[allow(unused_imports)]
struct MyStrangeStruct<T> {
items: Vec<T>,
age: u8,
}
fn main() {
let my_s = MyStrangeStruct {
age: 20,
items: vec![String::from("hello")],
};
println!("{:#?}", my_s);
}
In the previous example we could have defined the Debug
trait ourselves if needed. This is useful if you are dealing with a type that does not already implement this trait. A common occurance is the need to implement the trait Display
especially when printing them out to STDOUT
.`
#[allow(unused_imports)]
struct MyStrangeStruct<T> {
items: Vec<T>,
age: u8,
}
impl<T> fmt::Debug for MyStrangeStruct<T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MyStrangeStruct<T>")
.field("age", &self.age)
.field("items", &self.items)
.finish()
}
}
In this example, the goal is to be able to maintain a collection containing elements of different types.
use std::clone;
use std::default::Default;
use std::error;
use std::fmt;
use std::marker::Copy;
#[derive(Debug, Copy, Clone)]
pub enum State {
Up,
Down,
Unknown,
}
#[derive(Debug, Copy, Clone)]
pub struct PollHTTPBodyContent;
#[derive(Debug, Copy, Clone)]
pub struct PollHTTPStatusOk;
pub trait Monitorable {
fn info(&self) -> String;
fn poll(&self);
}
pub struct Monitor<T> {
context: T,
state: State,
}
pub struct Monitored {
pub enabled: Vec<Box<dyn Monitorable>>,
}
impl Default for State {
fn default() -> Self {
State::Unknown
}
}
impl Default for Monitored {
fn default() -> Monitored {
Monitored {
enabled: Vec::new(),
}
}
}
impl fmt::Debug for Monitored {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_list().entries(&self.enabled).finish()
}
}
impl fmt::Debug for dyn Monitorable {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Monitorable")
.field("info", &self.info())
.finish()
}
}
impl Monitored {
pub async fn new() -> Result<Monitored, Box<dyn error::Error>> {
Ok(Monitored::default())
}
pub async fn add<T: 'static + Monitorable>(
&mut self,
item: Box<T>,
) -> Result<(), Box<dyn error::Error>> {
self.enabled.push(item);
Ok(())
}
}
// PollHTTPBodyContent
impl fmt::Debug for Monitor<PollHTTPBodyContent> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Monitor<PollHTTPBodyContent>")
.field("state", &self.state)
.field("context", &self.context)
.finish()
}
}
impl Default for Monitor<PollHTTPBodyContent>
where
State: Default,
{
fn default() -> Monitor<PollHTTPBodyContent> {
Monitor::<PollHTTPBodyContent> {
context: PollHTTPBodyContent {},
state: State::default(),
}
}
}
impl Copy for Monitor<PollHTTPBodyContent> {}
impl clone::Clone for Monitor<PollHTTPBodyContent> {
fn clone(&self) -> Self {
*self
}
}
impl Monitorable for Monitor<PollHTTPBodyContent> {
fn info(&self) -> String {
String::from("Monitor<PollHTTPBodyContent>")
}
fn poll(&self) {
println!("poll() for {:#?}", self);
}
}
impl Monitor<PollHTTPBodyContent> {
pub async fn new() -> Result<Monitor<PollHTTPBodyContent>, Box<dyn error::Error>> {
let monitor: Monitor<PollHTTPBodyContent> = Monitor::<PollHTTPBodyContent>::default();
Ok(monitor)
}
}
// PollHTTPStatusOk
impl fmt::Debug for Monitor<PollHTTPStatusOk> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Monitor<PollHTTPStatusOk>")
.field("state", &self.state)
.field("context", &self.context)
.finish()
}
}
impl Default for Monitor<PollHTTPStatusOk>
where
State: Default,
{
fn default() -> Monitor<PollHTTPStatusOk> {
Monitor::<PollHTTPStatusOk> {
context: PollHTTPStatusOk {},
state: State::default(),
}
}
}
impl Copy for Monitor<PollHTTPStatusOk> {}
impl clone::Clone for Monitor<PollHTTPStatusOk> {
fn clone(&self) -> Self {
*self
}
}
impl Monitorable for Monitor<PollHTTPStatusOk> {
fn info(&self) -> String {
String::from("Monitor<PollHTTPStatusOk>")
}
fn poll(&self) {
println!("poll() for {:#?}", self);
}
}
The above allows adding separate Monitor
types to the Vector accumulator in Monitored
pub async fn run() -> Result<(), Box<dyn error::Error>> {
let mut monitored = Monitored::new().await?;
let new_monitor: Monitor<PollHTTPBodyContent> = Monitor::<PollHTTPBodyContent>::new().await?;
let new_monitor2: Monitor<PollHTTPStatusOk> = Monitor::<PollHTTPStatusOk>::new().await?;
monitored.add(Box::new(new_monitor)).await?;
monitored.add(Box::new(new_monitor2)).await?;
println!("Monitored: {:#?}", &monitored);
println!("Enabled monitor count: {}", &monitored.enabled.len());
for item in monitored.enabled.iter() {
item.poll();
}
Ok(())
}
// Monitored: [
// Monitorable {
// info: "Monitor<PollHTTPBodyContent>",
// },
// Monitorable {
// info: "Monitor<PollHTTPStatusOk>",
// },
// ]
// Enabled monitor count: 2
// poll() for Monitor<PollHTTPBodyContent> {
// state: Unknown,
// context: PollHTTPBodyContent,
// }
// poll() for Monitor<PollHTTPStatusOk> {
// state: Unknown,
// context: PollHTTPStatusOk,
// }
https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html
&
is immutable,&mut
is mutable. Mutable refs (references) cannot coexist with other refs thus,&
can also be referred to as shared or shared references, while&mut
is exclusive. This is sort of core to what makes Rust different from most languages.T
is called ageneric type parameter
.
use std::fmt;
use std::ops;
#[derive(Debug)]
#[allow(unused_imports)]
struct MyStrangeStruct<T> {
items: Vec<T>,
age: u8,
}
fn main() {
let mut input_a = 4.2;
let a: f64 = my_func_mut(&mut input_a, &mut 5.);
println!("a has {:.2}", a);
let b: f64 = my_func_as_ref(&2.1, &12.7);
println!("b has {:.2}", b);
}
fn my_func_mut<T>(input_a: &mut T, input_b: &mut T) -> T
where
T: fmt::Debug + ops::Add<Output = T> + Copy + From<f64>,
{
// Example of modifying contents of a mutable reference.
let inc_val = 0.6;
*input_a = *input_a + T::from(inc_val);
// `T::from` above works only because this trait has been defined on the `generic type parameter` `T`.
println!("my_func_mut: input_a has {:#?}", input_a);
*input_a + *input_b
}
fn my_func_as_ref<T>(input_a: &T, input_b: &T) -> T
where
T: fmt::Debug
+ ops::Add<Output = T>
+ ops::Sub<Output = T>
+ ops::Mul<Output = T>
+ ops::Div<Output = T>
+ Copy,
{
println!("my_func_as_ref: input_a has {:#?}", input_a);
*input_a / *input_b
}
use std::fmt;
#[derive(Debug)]
struct WrappedString(std::string::String);
impl fmt::Pointer for WrappedString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ptr = self as *const Self;
fmt::Pointer::fmt(&ptr, f)
}
}
impl fmt::Display for WrappedString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Tip: the use of `self.0` below allows use to access the inner `String`
// as WrappedString acts as a wrapper around `String`. This is an example
// of using the Newtype pattern.
// Bonus: had we just called `self`, notice that we cause recursion.
// Use of the newtype prevents recursion as Display is called by the
// inner `String`, rather than by `WrappedString`.
//
// References:
// - https://doc.rust-lang.org/stable/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types
// - https://doc.rust-lang.org/stable/book/ch19-04-advanced-types.html?highlight=newtype#using-the-newtype-pattern-for-type-safety-and-abstraction
write!(f, "{}", self.0)
}
}
impl std::ops::Add<&str> for WrappedString {
type Output = WrappedString;
fn add(self, other: &str) -> WrappedString {
let mut ws = self;
ws.0.push_str(other);
ws
}
}
impl WrappedString {
fn new(s: String) -> WrappedString {
WrappedString(s)
}
}
fn main() {
// `add()` is called at: https://github.com/rust-lang/rust/blob/master/src/liballoc/string.rs#L1996
// this calls `push_str` at https://github.com/rust-lang/rust/blob/master/src/liballoc/string.rs#L836
//
// Ref: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html#ways-variables-and-data-interact-move
// Demonstrating images 4-3 and 4-4:
// s1 got moved into add(), mutated, and then returned and you assigned it
// to combined, so conceptually it's the same String, but it's not at the
// same location. This is due to the move by assigning it to a new variable
// triggers the extra stack allocation.
let s1 = WrappedString::new("Howdy".to_string());
let s2 = String::from(" fella Rustacean!");
println!("s1 address {}", format!("{:p}", &s1));
println!("s2 address {}", format!("{:p}", &s2));
let combined = s1 + &s2;
println!("combined address {}", format!("{:p}", combined));
println!("combined result: {}", combined);
}
https://gist.github.com/bsodmike/a41f55e067292fd32960a9861b19e135
- Example of adding custom logic around the default panic handler (Link). This is useful to send panic reports via email before the executable terminates.
- Example of nesting
serde_json::Value
within aHashMap
(Link)
- Incredible rust book, yes, read it all! and read it again! https://doc.rust-lang.org/book/
- Rust by Example - this may not be as upto date as the book though https://doc.rust-lang.org/rust-by-example/index.html
- Talk by Jon Gjengset (@jonhoo) https://www.youtube.com/watch?v=DnT-LUQgc7s
- Rust Standard Library (stdlib) docs https://doc.rust-lang.org/std/
- Rust docs for any crate: https://docs.rs/
This gist has been compiled based on my learning from picking up Rust in just a couple days, which would have not been any easier had it not been for the monumental support found over at ##rust
on Freenode IRC. This is a big thank you to all their continued support.