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