Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Serde - 23rd of July

cargo add serde -F derive

Gives you a powerful way to serialize and deserialize data in Rust.

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize)]
struct Hans {
    is_boss: bool,
    company: String,
}
}

And with helping crates like serde_json:

cargo add serde_json

You can use serde_json to serialize and deserialize JSON data.

#![allow(unused)]
fn main() {
fn serialize(hans: &Hans) -> Result<String, serde_json::Error> {
    serde_json::to_string(hans)
}
}
fn main() {
    let hans = Hans {
        is_boss: true,
        company: "EHDS".to_string(),
    };

    match serialize(&hans) {
        Ok(json) => println!("Serialized JSON: {}", json),
        Err(err) => println!("Serialization error: {}", err),
    }
}

You can also use serde_json to deserialize JSON data.

#![allow(unused)]
fn main() {
fn deserialize(json: &str) -> Result<Hans, serde_json::Error> {
    serde_json::from_str(json)
}
}
fn main() {
    let json = r#"{"is_boss": true, "company": "EHDS"}"#;
    match deserialize(json) {
        Ok(hans) => println!("Deserialized Hans: {:?}", hans),
        Err(err) => println!("Deserialization error: {}", err),
    }
}

An example from live code

You can also use serde's traits to build more powerful data structures. You can have a look at the post method. It takes a T, that implements the serde::Serialize trait.

So the post method takes any T, that is serializable.

#![allow(unused)]
fn main() {
pub trait HttpsClient {
    fn get(&self, request: GetRequest) -> Result<GetResponse, FailureType>;
    fn post<T: 'static + serde::Serialize>(
        &self,
        request: PostRequest,
        content: &T,
    ) -> Result<PostResponse, FailureType>;
    fn post_file(&self, request: PostFileRequest) -> Result<PostResponse, FailureType>;
    fn delete(&self, request: DeleteRequest) -> Result<DeleteResponse, FailureType>;
}
}

Reqwest - 30th of July

Needed to run async code:

cargo add tokio -F full

No worries. Tokio will be explained in the next weeks. For now you only need to know we need an async runtime, that runs our async code.

cargo add reqwest

Is an easy to use http client for Rust.

How to use it

Below snippet will fetch the HTML from Google and print it to the console.

#[tokio::main]
async fn main() {
    let html_from_google = reqwest::get("https://www.google.com")
        .await
        .unwrap()
        .text()
        .await
        .unwrap();

    println!("{html_from_google}")
}

The example lives in reqwest-crate directory. To run it, use

cargo run --bin simple

How it's normally used

You can also combine serde with reqwest to deserialize JSON responses when fetching data, or serialize data, if you want to send data to a server.

cargo add serde -F derive
cargo add reqwest -F json

Getting data

use serde::Deserialize;

const URL: &str = "https://pokeapi.co/api/v2/pokemon";

#[derive(Deserialize, Debug)]
struct Pokemon {
    name: String,
    height: u32,
}

#[tokio::main]
async fn main() {
    let pokename = "bulbasaur";
    let url_with_endpoint = format!("{}/{}", URL, pokename);
    let pokemon: Pokemon = reqwest::get(url_with_endpoint) // << will get the response
        .await
        .unwrap() // << will crash, if we have an error fetching the data
        .json() // << will deserialize the response body into a Pokemon struct
        .await
        .unwrap(); // << will crash, if we have an error deserializing the data

    println!("{pokemon:?}")
}

You can run this example by running (from reqwest-crate directory)

cargo run --bin with_json

It should print something like this:

Pokemon { name: "bulbasaur", height: 7 }

More sophisticated things

You can also utilize reqwest's builders to create more complex requests.

#![allow(unused)]
fn main() {
// This will POST a body of `foo=bar&baz=quux`
let params = [("foo", "bar"), ("baz", "quux")];
let client = reqwest::Client::new();
let res = client.post("http://httpbin.org/post")
    .form(&params)
    .send()
    .await?;
}

This will send a form to the given URL.

See reqwest for more.

Clap - 6th of August

cargo add clap -F derive

Clap is useful, if you want to write a CLI program, that accepts arguments and options.

Clap offers two ways of defining arguments and options for your program:

  • Using the derive feature of clap: reference
  • Using the builder method, which is more flexible and allows for more complex configurations: reference

For simplicity, we will look at the derive feature of clap.

How to use Clap

For a simple program, you define a struct, and it's fields become the arguments and options of your program. If you use the derive feature of clap, you can use the Parser derive macro to automatically generate the clap parser for your struct and you can instantly access the arguments and options.

An example from their docs:

use clap::Parser;

/// Simple program to greet a person
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
    /// Name of the person to greet
    #[arg(short, long)]
    name: String,

    /// Number of times to greet
    #[arg(short, long, default_value_t = 1)]
    count: u8,
}

fn main() {
    let args = Args::parse();

    for _ in 0..args.count {
        println!("Hello {}!", args.name);
    }
}

If you now run this program with no arguments, Args::parse() will panic, as it expects an argument with --name, which is not an optional, therefore required argument.

But how can you give args to your Rust program:

  • Via your binary (usually in your target directory):
./<you_binary_name> --name Bernd
  • Via cargo
cargo run -- --name Bernd
  • Installing your program (will be installed in ~/.cargo/bin, which is in your PATH) - then use it:
cargo install --path .
<your-binary-name> --name Bernd

Try it out

Generate a new rust project:

cargo new clap_project

Add clap as a dependency:

cargo add clap -F derive

Play around with it :). You can have a look at the clap examples.

Axum - 13th of August

Axum is a powerfull, yet easy to use web framework for Rust, that is built on top of the Tokio runtime. It's quite modular. You can for example create middlewares with another great crate called tower.

What you usually need:

  • The async runtime: cargo add tokio -F full
  • JSON: cargo add serde -F derive

And axum itself: cargo add axum

Howto

Source

The main entry point of axum is the router, where you define your endpoints.

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", get(root))
        .route("/users", post(create_user));

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

Here we have two endpoints:

  • /: This endpoint to the root URL ("/"). It is mapped to the root function.
  • /users: This endpoint is used to handle HTTP POST requests to the "/users" URL. It is mapped to the create_user function, which accepts JSON as input (will be extracted) and creates a JSON response from it.

The callback functions themselves are responsible for handling the incoming requests, where e.g. Path variables, Query variables or Json can be extracted. Those functions are responsible for generating the appropriate responses, that can be turned into a HTTP response.

For example the root function returns just a string:

#![allow(unused)]
fn main() {
async fn root() -> &'static str {
    "Hello, World!"
}
}

Whereas the create_user accepts a JSON input (extracts it from the request) and returns a JSON response:

#![allow(unused)]
fn main() {
#[derive(Deserialize)]
struct CreateUser {
    username: String,
}

#[derive(Serialize)]
struct User {
    id: u64,
    username: String,
}

async fn create_user(
    Json(payload): Json<CreateUser>, // JSON-Extractor
) -> (StatusCode, Json<User>) {
    let user = User {
        id: 1337,
        username: payload.username,
    };

    (StatusCode::CREATED, Json(user))
}
}

If you run the example in axum-crate with cargo run. It will open up a web server at http://localhost:3000. You can curl to test it:

curl -X POST -H "Content-Type: application/json" -d '{"username": "john"}' http://localhost:3000/users

or (to call the root endpoint)

curl -X GET http://localhost:3000

Other examples

You can find more examples, like graphql, chats, or using ORMs etc in the axum repository.