presentation-rust-webapps/rust_web.org
2021-04-14 23:02:09 +02:00

23 KiB

Rust, Rocket and Diesel

Rust

What is Rust?

#+LaTeX:∈cludegraphics[width = 0.65\textwidth]{img/Bruine_roest_op_tarwe_(Puccinia_recondita_f.sp._tritici_on_Triticum_aestivum).jpg}

Rust, the language

  • About 10 years young (2010)
  • Memory safe without gcing, optional ref counting
  • Ownership, lifetimes, traits, functional paradigms
  • Variables are immutable by default and can be shadowed
  • Performance of idiomatic Rust is comparable to the performance of idiomatic cpp

How to install

Install Rust

$ curl --proto '=https' --tlsv1.2 -sSf \
  https://sh.rustup.rs | sh

Install nightly Rust (needed for Rocket)

$ rustup toolchain install nightly

Install rustfmt (linter and code formatter)

$ rustup component add rustfmt

Install clippy (static code analysis)

$ rustup component add clippy

Where to find more information?

Docs…

Hello, World!

Executing `cargo new foobar` creates a new project…

foobar/Cargo.toml

[package]
name = "foo"
version = "0.1.0"
authors = ["finga <finga@onders.org>"]
edition = "2018"

\tiny

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

\normalsize

[dependencies]

foobar/src/main.rs

fn main() {
    println!("Hello, world!");
}

Mutability and shadowing

Immutable variable

let foo = 5;

Mutable variable

let mut bar = 5;
bar = 6;

Shadowing

let baz = 5;
let baz = baz + 3;

Structs

Definition of a struct

struct Something {
    id: usize,
    item: String,
}

Instantiation of a struct

let something = Something {
    id: 0,
    item: String::from("an item"),
};

Access of a struct's field

something.item

Tuple structs

Definition of a tuple struct

struct Box(usize, usize, usize);

Instantiation of a tuple struct

let first_box = Box(3, 5, 7);

Access of a tuple struct's field

first_box.0;
first_box.1;
first_box.2;

Error related types

  • Heavy use of Option<T> and Result<T, E>

Option

pub enum Option<T> {
    None,
    Some(T),
}

Result

pub enum Result<T, E> {
    Ok(T),
    Err(E),
}

Error handling

Unrecoverable errors → panics

fn get_data() -> Data {
    ...
    panic!("Cannot get data");
}

Recoverable error with anyhow

fn get_data() -> Result<Data> {
    ...
    Ok(data) // return is only used for "early" returns
}
...
    let data = get_data()?;
...

match statements

  • Must be exhaustive
  • All match arms must return the same type!
  • Can be used everywhere e.g. let foo = match ...
  • All match arms must return the same type!!1!

Recoverable error with anyhow

match age {
    0 => unreachable!("Too young to be a person"),
    n @ 1..=12 => println!("Child of age {}", n),
    n @ 13..=19 => println!("Teen of age {}", n),
    n @ 20..=150 => println!("Person of age {}", n),
    _ => unimplemented!("Nobody I know got that old"),
}

if let statements

Sometimes an if let is nicer than a match or an if/else statement

if let Some(actual_age) = age {
    println!("The age is {}", actual_age);
}

Use functional language features

  • vec![1, 2, 3].iter()
  • vec![1, 2, 3].iter().sum()
  • vec![1, 2, 3].iter().map(|x| x + 1).collect()
  • foo.into_iter().filter(|i| i.bar == 0).collect()
  • Iterator trait

Tests

  • Use assert!() family
  • Unit tests

    • Put them into a test module
    • Annotate the test module with #[cfg(test)]
    • Annotate the tests with #[test]
  • Documentation testing
  • Integration testing

    • Integration tests are put into the src/tests/ directory

Docs

Rocket

What is Rocket?

Rocket is a web framework for Rust that makes it simple to write fast, secure web applications without sacrificing flexibility, usability, or type safety.

Features

Col left
  • Type Safe
  • Extensible
  • Templating
  • (Secure) Cookies
Col right
  • Streams
  • Testing (Unit tests)
  • Typed URIs
  • Single binary

Create a new Rocket project

Create the crate

cargo init new_project
cd new_project
rustup override set nightly
cargo r

Add Rocket dependencies to Cargo.toml

[dependencies]
rocket = "0.4"

Hello Rocket

src/main.rs

#![feature(decl_macro)]

use rocket::{get, routes};

#[get("/hello/<name>/<age>")]
fn hello(name: String, age: u8) -> String {
    format!("Hello, {} year old named {}!", age, name)
}

fn main() {
    rocket::ignite().mount("/", routes![hello]).launch();
}

Receive data

Struct EmailForm

#[derive(Debug, FromForm)]
struct EmailForm {
    email: String,
}

Procedure using that form data

#[post("/reset", data = "<email>")]
fn reset_email(email: Form<EmailForm>) -> Flash<Redirect> {
    ...
    Flash::success(
        Redirect::to(uri!(reset)),
        format!("Email was sent to {}", email.email),
    )
}

Flash messages

#[get("/reset")]
fn reset(flash: Option<FlashMessage>) -> String {
    if let Some(ref msg) = flash { // `let ref x = y` is
        msg.msg()                  // equivalent to `let x = &y`
    } else {
        format!("Hello, reset!");
    }
}

Templates

  • Use the rocket_contrib crate!

templates/name.html.tera

<!DOCTYPE html>
<html>
  <head>
    ...
  </head>
  <body>
    <p> Hello,
    {{ name }}!</p>
  </body>
</html>

src/main.rs

#[get("/hello/<name>")]
fn hello(name: String) -> Template {
    let mut context = HashMap::new();
    context.insert("name", name);

    Template::render("name", &context)
}
...
    rocket::ignite()
        .mount(...)
        .attach(Template::fairing())
        .launch();

Where to get further information?

Diesel

ORM

Object-relational-mapping is a programming technique for converting data between incompatible type systems using the paradigms of your programming language.

Pros

  • Models are DRY (don't repeat yourself)
  • Cleaner separation
  • Prepared and sanitised queries
  • Changes are versioned

Cons

  • Debugging can get more complicated (performance)
  • No need to learn SQL

Prerequisites diesel_cli

Setup Diesel for default DBMS

$ sudo apt install libpq-dev libsqlite3-dev \
  default-libmysqlclient-dev
$ cargo install diesel_cli

Setup Diesel for SQLite only

$ sudo apt install libsqlite3-dev
$ cargo install diesel_cli -no-default-features \
  --features sqlite

How does that look in Rust

Define a struct

#[derive(Queryable)]
pub struct NewComment<'a> {
    name: &'a str,
    email: &'a str,
    comment: &'a str,
}

Insert something

let user = NewUser {
    name: "foo",
    email: "foo@bar.baz",
    comment: "A comment",
};

...

insert_into(comments)
    .values(&user)
    .execute(conn);

About Diesel

  • Enable connection pooling via the r2d2 crate
  • Nested joins are not yet nicely implemented
  • Database specific SQL regarding each DBMS
  • Take a look at the diesel_cli crate for multi DBMS support
  • Use diesel_cli macros and functions for compiling migration functionality into the application

Where to get further information?

Lets code something

Lets create a small web app

A chat box where everybody can post.

#+LaTeX:∈cludegraphics[width = 0.7\textwidth]{img/challenge-accepted-lets-code-it.jpg}

Conclusion

In general

  • Rust's community and ecosystem is growing steadily
  • Rust covers a broad field from low level code to high level
  • Rocket and libraries alike improve creating security and performance focused web application development
  • Diesel is on a good way to become a serious ORM
  • Alltogether Rust is ready to be used for web apps

What did we do?

  • Setup Rust and Diesel
  • Create a project with Rocket and Diesel prerequisites
  • Store a small model with Diesel in a SQLite DB
  • Create and use Tera templates

Where to continue

  • Move DB setup into the project
  • Add features (List posts by Author, paging, etc..)
  • (Private) Cookies

Two small projects

ldap0r

  • LDAP password reset tool
  • ($\sim$ 300 SloC without tests)
  • First release
  • Tests

webhookey

  • Execute scripts triggered by http(s) requests
  • Is becoming more and more robust
  • ($\sim$ 400 SloC without tests)

The End

#+LaTeX:∈cludegraphics[width = 0.3\textwidth]{img/goodbye.jpg}

\Huge Thanks for your attention!!1!

This is the end..