Rust - A beginner's take

### Introduction

Disclaimer: Author coming from a frontend background and with very limited backend exposure. However a couple of months ago he decided to take a dive into the Rust world and thought it wouldn't be a very bad idea to share with you all his first little discoveries :) 

So as it probably becomes quite obvious from the disclaimer in this article I would like to share with you some interesting challenges I 've come to face during these first 2 months working with Rust which I believe might be helpful for people who are just setting off on their Rust journey. I would like to think that even people with more experience with the language will find some of these insights not completely trivial.

My idea is to walk you through some simple examples, where we can use different features of the language but also talk a little bit about the thinking process behind the implementation. Before going through the examples I should probably mention that we are going to use `diesel` (http://diesel.rs/) for everything that involves fetching/updating/inserting records to our `Postgres` database and I highly recommend everyone to check this guide first http://diesel.rs/guides/getting-started/ if they would also like to code along while reading through the examples that follow. 

### Challenge #1

Alright and now that all the disclaimers are out let's start with our first example. So the challenge here goes something like this. Let's say we have a `users` table and for each user we also store their password (among other fields). There can only be **one** valid password per user so every time a user tries to update their password we need to invalidate the previous valid one and add a new record for this user with the new password. We also have another field called `pass_count` which essentially counts how many times a user has changed their password. This means that every time we add a new password for a user we need to check also the `pass_count` value stored together with the to-be-invalidated password and when adding the new password increment the `pass_count` value by 1.

The reason why we don't directly overwrite the previous record for the same user is that we want to be able to trace back all the times that this user tried to update their password and have a record of all the passwords that they have used. 

### Setting up the project with `diesel`

Ok so before jumping to the implementation, I will try to create here a simple project with all the setup that we need to work with. Please feel free to follow these steps as well or you can also just go and check the implementation directly. The process for creating our demo project is very similar to the one described in this guide http://diesel.rs/guides/getting-started/ so instead of repeating here all those steps again, I will just list down the changes that we need to make, to tailor it to our needs.

* Since we are going to work with a `users` table when creating the migration run:

```bash
diesel migration generate create_users
```

* The `users` table inside our `up.sql` file should look like:

_up.sql_
  
```rust
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR NOT NULL,
    password VARCHAR NOT NULL,
    status_flg BOOLEAN NOT NULL,
    pass_count INTEGER NOT NULL
)
```

* Also inside our `down.sql` file we should add:

_down.sql_

```rust
DROP TABLE users
```

* The code inside the `schema.rs` file is auto-generated after running `diesel migration run` and should look like this:

_src/schema.rs_

```rust
table! {
    users (id) {
        id -> Int4,
        username -> Varchar,
        password -> Varchar,
        status_flg -> Bool,
        pass_count -> Int4,
    }
}  
```

* Now let's create our `models.rs` file and inside there let's add the structs we are going to work with for this example:

_src/models.rs_

```rust
use super::schema::users;

#[derive(Queryable, Debug)]
pub struct User {
    pub id: i32,
    pub username: String,
    pub password: String,
    pub status_flg: bool,
    pub pass_count: i32,
}

#[derive(Insertable)]
#[table_name=“users”]
pub struct NewUserEntry {
    pub username: String,
    pub password: String,
    pub status_flg: bool,
    pub pass_count: i32,
}
```

* Finally in order to have a nice structure for our project, under `src` directory let's create a `main.rs` file where we 're going to put our `main()` function as well as an `api` directory inside which we 're going to put our api that incorporates the logic described above. Inside the `api` directory let's create a new file with our api - let's call it `update_password.rs` - and a `mod.rs` file that makes our api visible to external crates:

_src/api/mod.rs_

```rust
pub mod update_password;
```  

### Implementing the `update_password` api

Now we are finally ready to implement our `update_password.rs` api and our `main.rs` that calls it. Let me first post the code here and I will further explain some of this code right after.

_src/main.rs_

```rust
use anyhow::Result; // add anyhow to your dependencies in Cargo.toml
mod api;

fn main() -> Result<()> {
    let username = String::from("username");
    let new_password = String::from("new_password"); // Friendly reminder: This is just for demonstration purposes
    api::update_password::run(username, new_password)?; // here we call our api
    Ok(())
}
``` 

_src/api/update___password.rs_

```rust
extern crate diesel_demo;
extern crate diesel;
use self::diesel_demo::*;
use self::diesel::prelude::*;
use diesel_demo::models::*;
use diesel_demo::schema::users;
use anyhow::Result;

pub fn run(username: String, password: String) -> Result<()> {
    let conn = establish_connection();
    let query = users::table.filter( // constructs the query
        users::username.eq(username.clone()).and(users::status_flg.eq(true))
    );
    let new_pass_count = diesel::update(query)
        .set(users::status_flg.eq(false)) // here we specify which columns we want to update
        .returning(users::pass_count) 
        .get_result::<i32>(&conn) // if used without the returning clause would return the whole updated record - here it returns only the pass_count value
        .optional()? // we use optional cause we cannot be sure if the record exists or not
        .map_or_else(
            || 1, // we set the pass_count to 1 if record not found
            |num| num + 1, // the new pass_count value should be the current pass_count value + 1
        );
    let new_entry = NewUserEntry { // a different struct is used for inserting a new user entry
        username,
        password,
        status_flg: true, 
        pass_count: new_pass_count,
    };
    diesel::insert_into(users::table).values(&new_entry).execute(&conn)?;
    Ok(())
}
```

### `get_result` + `returning` combo

Ok so looking more carefully into the `update_password` implementation we can see some of the powerful features we get by using `diesel` with `Postgres` in this case. Here since we are only interested in the `pass_count` value, we can use the `get_result` api along with the `returning` clause when executing the UPDATE operation to invalidate the user's current password, so that we get back only the `pass_count` value instead of the whole record. Also since we are not sure whether the record exists or not, we can use `.optional()` on the result and chain it with `map_or_else` so that we set the `new_pass_count` value directly:

* `1`, if there is no record for that user yet
* `pass_count + 1`, if the record was found 

Now that we have the `new_pass_count` value stored, we can insert a new record with the new password for that user. Notice that for the INSERT operation a different `NewUserEntry` struct is used (also previously defined by us in the `model.rs` file).  

### `build_transaction`

Ok seems like we 're all set..we invalidated the previous password for that user and our user now can continue using our services with their new password! Well... It's true that our code now works as expected, however having our api handling an UPDATE and an INSERT operation in this way is not very safe and that's because a different service might try to access our database right after the UPDATE operation is finished and before the INSERT operation takes place, leading to unpredictable behavior. To prevent such a scenario we need to wrap those two operations in a transaction and make the whole thing atomic. Let's edit our code so that we guard our api against such funky outcomes:

```rust

pub fn run(username: String, password: String) -> Result<()> {
    let conn = establish_connection();
    conn.build_transaction().repeatable_read().run(|| {
        let query = users::table.filter( // constructs the query
            users::username.eq(username.clone()).and(users::status_flg.eq(true))
        );
        ...
        diesel::insert_into(users::table).values(&new_entry).execute(&conn)?;    
    })

    Ok(())
}
```      

As we see, this can be done in one line with diesel `conn.build_transaction().repeatable_read().run(|| {...})` , where we build the transaction specifying different parameters (like in this case that we set REPEATABLE READ as the ISOLATION LEVEL) and we call `run` on the builder to run the code wrapped in the transaction. Here basically everything related to the two operations (UPDATE and INSERT) becomes part of one bigger transaction. 

Alright and now seems like we got all the bases covered, hopefully with no caveats this time. And actually I just realized that this article is getting too long, so better stop here for now. We 'll continue with more examples next time(?) Jaaa mata ne!