On Github thoughtram / rust-and-nickel
You're gonna learn from a Rust noob
"Rust is a systems programming language that runs blazingly fast, prevents almost all crashes*, and eliminates data races."
* In theory. Rust is a work-in-progress and may do anything it likes up to and including eating your laundry.
Tell me more!
class Circle { constructor(radius) { this.radius = radius; } calcArea () { return Math.PI * this.radius * this.radius; } }
function calcCircles(numberOfCircles, radius) { var area = 0; for (var i = 0; i < numberOfCircles; i++) { let circle = new Circle(radius); area += circle.calcArea(); } return area; } calcCircles(3, 5);
Heap Memory ┌─────────────────────────────────┐ │ │ ├─────────────────────────────────┤ │ │ ├─────────────────────────────────┤ │ │ ├─────────────────────────────────┤ │ │ └─────────────────────────────────┘
function calcCircles(numberOfCircles, radius) { var area = 0; for (var i = 0; i < numberOfCircles; i++) { let circle = new Circle(radius); area += circle.calcArea(); } return area; } calcCircles(3, 5);
Heap Memory ┌─────────────────────────────────┐ │ new Circle (radius) │ ├─────────────────────────────────┤ │ │ ├─────────────────────────────────┤ │ │ ├─────────────────────────────────┤ │ │ └─────────────────────────────────┘
function calcCircles(numberOfCircles, radius) { var area = 0; for (var i = 0; i < numberOfCircles; i++) { let circle = new Circle(radius); area += circle.calcArea(); } return area; } calcCircles(3, 5);
Heap Memory ┌─────────────────────────────────┐ │ new Circle (radius) │ ├─────────────────────────────────┤ │ new Circle (radius) │ ├─────────────────────────────────┤ │ │ ├─────────────────────────────────┤ │ │ └─────────────────────────────────┘
function calcCircles(numberOfCircles, radius) { var area = 0; for (var i = 0; i < numberOfCircles; i++) { let circle = new Circle(radius); area += circle.calcArea(); } return area; } calcCircles(3, 5);
Heap Memory ┌─────────────────────────────────┐ │ new Circle (radius) │ ├─────────────────────────────────┤ │ new Circle (radius) │ ├─────────────────────────────────┤ │ new Circle (radius) │ ├─────────────────────────────────┤ │ │ └─────────────────────────────────┘
function calcCircles(numberOfCircles, radius) { var area = 0; for (var i = 0; i < numberOfCircles; i++) { let circle = new Circle(radius); area += circle.calcArea(); } return area; } calcCircles(3, 5); //some time later
Heap Memory ┌─────────────────────────────────┐ │ │ ├─────────────────────────────────┤ │ │ ├─────────────────────────────────┤ │ │ ├─────────────────────────────────┤ │ │ └─────────────────────────────────┘
Eventually GC cleans it up
/* var vehicle = { name: 'Schoolbus', passenger: [] };*/ var vehicle = getVehicle(); var bookingService = new BookingService(vehicle); var repairService = new RepairService(vehicle);
Now both bookingService and repairService hold a reference to vehicle
let vehicle = get_vehicle(); let booking_service = BookingService::new(vehicle); let repair_service = RepairService::new(vehicle);
Doesn't compile!
error: use of moved value: `vehicle` RepairService::new(vehicle); ^~~~~~~ note: `vehicle` moved here because it has type `Vehicle`, which is non-copyable BookingService::new(vehicle);
How am I supposed to write any code?!
let vehicle = get_vehicle(); let booking_service = BookingService::new(&vehicle); let third_service = ThirdService::new(&vehicle);
Safe memory management without GC
struct Dog;
Structs are like lightweight classes
struct Dog { name: String, age: i32 }
They may have fields
let yeti = Dog { name: "Yeti".to_owned(), age: 15 }
They can be instanziated
let yeti = Dog { name: "Yeti".to_owned() }
error: missing field: `age` [E0063] let yeti = Dog { name: "Yeti".to_owned() };
But not partially
struct Dog { name: String, age: Option<i32> }
Rust has the Option enum for that
let yeti = Dog { name: "Yeti".to_owned(), age: None };
Which makes it explicit (No NullPointerExceptions!)
struct Dog { name: String } impl Dog { fn greet (&self) { println!("My name is {}", self.name); } } let banjo = Dog { name: "Banjo".to_owned() }; banjo.greet();
They can have methods
trait Talk { fn talk (&self); }
Traits are like interfaces. They define a contract.
struct Human; struct Dog; struct Tree; impl Talk for Human { fn talk (&self) { println!("blabla"); } } impl Talk for Dog { fn talk (&self) { println!("bark bark"); } }
fn talk(talkable: &Talk) { talkable.talk(); } fn main() { let person = Human; let dog = Dog; let tree = Tree; talk(&person); talk(&dog); //compile time error since Tree doesn't impl Talk //talk(&tree); }
trait Talk { fn talk (&self) { println!("strange noises"); } } impl Talk for Cat {}
Traits can have default implementations
impl Talk for i32 { fn talk (&self) { println!("shifting bits around"); } } fn talk(talkable: &Talk) { talkable.talk(); } fn main() { talk(&3); }
Traits can be written for foreign types!
Isn't that wild west crazy?!
struct Person; struct Car; struct SmartPersonStorage; impl SmartPersonStorage { fn add (&self, item: Person) { /*code*/ } fn get (&self, id: i32) -> Person { /*code*/ } }
What if we need a SmartCarStorage, too?
struct Person; struct Car; struct SmartPersonStorage; struct SmartCarStorage; impl SmartPersonStorage { fn add (&self, item: Person) { /*code*/ } fn get (&self, id: i32) -> Person { /*code*/ } } impl SmartCarStorage { fn add (&self, item: Car) { /*code*/ } fn get (&self, id: i32) -> Car { /*code*/ } }
Give up DRY
:( sad face
struct Person; struct Car; struct SmartStorage; impl SmartStorage { fn add (&self, item: &Any) { /*code*/ } fn get (&self, id: i32) -> &Any { /*code*/ } }
Give up type safety
:( sad face
struct Person; struct Car; struct SmartStorage; impl SmartStorage<T> { fn add (&self, item: T) { /*code*/ } fn get (&self, id: i32) -> T { /*code*/ } }
Give up nothing
:) happy face
struct Person; struct Car; struct SmartStorage; impl SmartStorage<T: HasId> { fn add (&self, item: T) { /*code*/ } fn get (&self, id: i32) -> T { /*code*/ } }
Can use trait bounds
Let's refactor some earlier code!
fn talk(talkable: &Talk) { talkable.talk(); } fn main() { let person = Human; let dog = Dog; talk(&person); talk(&dog); }
Can pass any reference to anything that implements the Talk trait
struct Human; struct Dog; impl Talk for Human { fn talk (&self) { println!("blabla"); } } impl Talk for Dog { fn talk (&self) { println!("bark bark"); } }
Each struct has it's own talk implementation
fn talk(talkable: &Talk) { talkable.talk(); } fn main() { let person = Human; let dog = Dog; talk(&person); talk(&dog); }
fn talk(talkable: &Talk) { talkable.talk(); } fn main() { let person = Human; let dog = Dog; talk(&person); talk(&dog); }
Which implementation of talk() to call here?
You're talking about dynamic dispatch, right?!
fn talk(talkable: &Talk) { talkable.talk(); } fn main() { let person = Human; let dog = Dog; talk(&person); talk(&dog); }
fn talk<T: Talk>(talkable: T) { talkable.talk(); } fn main() { let person = Person; let dog = Dog; talk(person); talk(dog); }
fn talk<T: Talk>(talkable: T) { talkable.talk(); } fn main() { let person = Person; let dog = Dog; talk(person); talk(dog); }
talk is now generic with a Talk trait bound
fn talk_person(talkable: Person) { talkable.talk(); } fn talk_dog(talkable: Dog) { talkable.talk(); } fn main() { let person = Person; let dog = Dog; talk_person(person); talk_dog(dog); }
The compiler generates specialized code
That's why they call it static dispatch!
Zero-cost abstractions
(called the "sum type" of algebraic data types)
enum Error { Critical, NonCritical }
Error can either be Critical or NonCritical at any one time
fn log_error(error:Error) { match error { Error::Critical => println!("Critical error happened"), Error::NonCritical => println!("Relax, it's probably fine ;)") } } fn main() { log_error(Error::Critical); log_error(Error::NonCritical); }
enum HttpError { WithCode(i32), WithMessage(&'static str), WithCodeAndMessage(i32, &'static str) }
Variants may contain data of any other (mixed) types
fn log_http_error(error:HttpError) { match error { HttpError::WithCode(code) => println!("error with id {} occured", code), HttpError::WithMessage(msg) => println!("error with message {} occured", msg), HttpError::WithCodeAndMessage(code, msg) => println!("error with code {} and message {} occured", code, msg) } } fn main() { log_http_error(HttpError::WithCode(404)); log_http_error(HttpError::WithMessage("Service unavailable")); }
which we have access to via pattern matching
enum Result<T, E> { Ok(T), Err(E), }
Types can be generic
match File::open("foo.txt") { Ok(file) => {/*do something with file */}, Err(error) => {/* handle error (io::Error)*/} }
match json::decode(json_str)) { Ok(model) => {/*do something with model*/}, Err(error) => {/*handle error (json::DecoderError)*/} }
In fact the std library uses generic enums a lot
impl<T, E> Result<T, E> { fn is_err(&self) -> bool { !self.is_ok() } }
Enums can implement methods
impl <T: Clone, E: Clone> Clone for Result<T, E> { fn clone(&self) -> Result<T, E> { match &*self { &Result::Ok(ref val) => Result::Ok(val.clone()), &Result::Err(ref val) => Result::Err(val.clone()), } } }
Enums can implement traits
macro_rules! foo { (x => $e:expr) => (println!("mode X: {}", $e)); (y => $e:expr) => (println!("mode Y: {}", $e)); } fn main() { foo!(y => 3); }
Macros allow us to abstract at a syntactic level
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.get("/hello", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.get("/hello", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
Tells the compiler to include macros from nickel crate
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.get("/hello", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
Tells the compiler to include the nickel crate
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.get("/hello", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
Imports nickel's facade
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.get("/hello", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
Brings HttpRouter trait into scope
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.get("/hello", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
The rust program entry function
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.get("/hello", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
Creates nickel's facade object
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.get("/hello", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
Mutability must be explicit
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.get("/hello", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
Respond with hello world on 127.0.0.1:6767/hello
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.get("/hello", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
Start listening
middleware!("hello world")
handler without body
middleware!({ let first_name = "Pascal"; let last_name = "Precht"; format!("{} {}", first_name, last_name) })
handler with body
middleware! { |req| format!("Hello: {}", req.param("username")) }
Accessing request params
middleware! { |req, mut res| res.content_type(MediaType::Json); r#"{"foo":"bar"}"# }
Setting the content type
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.get("/hello", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.post("/hello", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.put("/hello", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.delete("/hello", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.option("/hello", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.get("/bar", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
matches /bar
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.get("/a/*/d", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
matches /a/b/d
BUT NOT /a/b/c/d
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.get("/a/**/d", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
matches /a/b/d
AND ALSO /a/b/c/d
#[macro_use] extern crate nickel; use nickel::Nickel; use nickel::router::http_router::HttpRouter; fn main() { let mut server = Nickel::new(); server.utilize(StaticFilesHandler::new("examples/assets/")); server.get("/hello", middleware!("hello world")); server.listen("127.0.0.1:6767"); }
Serves files from example/assets