Getting Started with Rust and Docker
Published: 2023-02-11 07:59:33

Rust has consistently been one of the most loved programming languages in the Stack Overflow Developer Survey. Rust tops StackOverflow Survey 2022 as the most loved language for the 7th year. In the 2021 survey, Rust was ranked as the 2nd most loved language, with 85.8% of Rust developers reporting that they want to continue using it. In the 2020 survey, Rust was also ranked 2nd, with 78.9% of Rust developers expressing that they loved the language. The popularity of Rust is due to its focus on performance and reliability, as well as its user-friendly and expressive syntax.

Rust is a systems programming language that was first developed by Mozilla Research. It is designed to be fast, reliable, and secure, with an emphasis on concurrent and parallel programming. Rust is known for its strong type system and its low-level control over system resources, making it well-suited for high-performance tasks such as game development, networking, and low-level system programming.

Rust is also known for its unique memory safety model, which eliminates the risk of many common programming errors, such as null pointer dereferences and buffer overflows. This is achieved through the use of ownership and borrowing, which allows Rust to enforce strict control over the lifetimes and ownership of values in the program.

In addition to its technical features, Rust has a large and active community, with many libraries and tools available for use in Rust projects. This makes it easy to get started with Rust, and to find the resources you need to build high-quality applications.

## Why is Rust so popular?

Rust has become popular for several reasons:

### 1. Memory safety

Rust's unique memory safety model helps to prevent a wide range of common programming errors, such as null pointer dereferences and buffer overflows. This makes Rust code more reliable and secure compared to code written in other systems programming languages.

### 2. Performance

Rust is designed to be fast, with low-level control over system resources. This makes it ideal for high-performance tasks, such as game development and low-level system programming.

### 3. Concurrency and parallelism

Rust has strong support for concurrent and parallel programming, which makes it well-suited for multi-threaded and parallel applications.

### 4. Community and ecosystem

Rust has a large and active community, with many libraries and tools available for use in Rust projects. This makes it easy to get started with Rust and finds the resources you need to build high-quality applications.

### 5. User-friendly error messages

Rust's error messages are designed to be helpful and user-friendly, making it easier for developers to understand and fix errors in their code.

### 6. Safe and stable code

Rust's focus on safety and stability, combined with its strict type system, helps to ensure that the code written in Rust is more likely to be correct and less prone to bugs.

Rust's combination of memory safety, performance, and ease of use makes it an attractive choice for developers who want to build high-quality, reliable, and secure applications.

## A simple Rust application

Here is a simple Hello World application in Rust:

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

## Explanation:

- fn main() is the entry point for the program. All Rust programs must have a main function, which is where execution starts.

- println!("Hello, World!") is a macro that writes the string "Hello, World!" to the console. The ! at the end of println! is used to indicate that this is a macro, not a function.

- ; at the end of a line of code is used to indicate the end of a statement.

To run this code, you would need to have the Rust programming language installed on your machine. You can then save this code to a file with a .rs extension, for example main.rs. Then you can run the following command in the terminal:

```
$ cargo run
```

This will compile and run the program, and the output will be Hello, World!. 

## Why do you need Cargo.toml file?

Cargo.toml is a configuration file for the Rust package manager, Cargo. It is used to specify the dependencies and build settings for a Rust project.

A typical Cargo.toml file for a Rust project includes information about the package, such as its name and version, as well as a list of dependencies that the project needs in order to build and run. Here is an example of a simple Cargo.toml file:

```
[package]
name = "my-rust-app"
version = "0.1.0"

[dependencies]
serde = "1.0"
```

This Cargo.toml file specifies that the project is named my-rust-app and has a version of 0.1.0. It also specifies a dependency on the serde library, version 1.0. When the Rust build process runs, Cargo will automatically download and manage these dependencies, making it easy to manage dependencies for your project.

## Writing a Cargo.toml file

```
cat Cargo.toml 
[package]
name = "my-rust-app"
version = "0.1.0"


[[bin]]
name = "my-rust-app"
path = "src/main.rs"
```

## Containerising a Rust program


To containerise a Rust application, you need to follow these steps:


## Write a Dockerfile

This file will specify the base image, any dependencies and how to compile and run your Rust application.

```
# Use a base image with the latest version of Rust installed
FROM rust:latest

# Set the working directory in the container
WORKDIR /app

# Copy the local application code into the container
COPY . .

# Build the Rust application
RUN cargo build --release

# Specify the command to run when the container starts
CMD ["./target/release/my-rust-app"]
```

## Build the Docker Image

Use the docker build command to build an image from the Dockerfile.

You can then build the Docker image with the following command:

```
docker build -t my-rust-app .
```

In the above Dockerfile, you would copy the local application code into the container with the following line:

```
COPY . .
```

This line copies all files in the current directory (.) to the /app directory in the container. The resulting Docker image will contain your Rust application, ready to be built and run.

## Running the Container

Use the docker run command to start a container based on the built image.


```
docker run --name my-rust-app -it my-rust-app
Hello, world!
```

## Rust and Docker Compose

Let's say you have a web application written in Rust that provides a REST API for retrieving information about books. You want to use Docker Compose to manage the containers for the Rust application and a database (e.g. PostgreSQL) that the application will use to store the book information.

Write the Rust application: Your Rust application will expose a REST API that allows clients to retrieve information about books from a database. The application will use the Rocket framework to handle HTTP requests and the Diesel ORM to interact with the database.

Here's an example of what a Cargo.toml file for a book API might look like:

```
[package]
name = "book-api"
version = "0.1.0"
authors = ["Your Name <your.email@example.com>"]

[dependencies]
rocket = "0.4.6"
diesel = { version = "1.4.4", features = ["postgres"] }
dotenv = "0.15.0"

[dependencies.rocket_contrib]
version = "0.4.6"
default-features = false
features = ["json"]
```

This file specifies the name, version, and authors of the application, as well as the dependencies it needs to build and run. In this case, the dependencies are the Rocket web framework, the Diesel ORM, and the dotenv library for reading environment variables.

Here's an example of what a main.rs file for a book API might look like:

```
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate rocket;

use diesel::prelude::*;
use diesel::pg::PgConnection;
use dotenv::dotenv;
use std::env;

use rocket::response::content::Json;
use rocket_contrib::json::JsonValue;

pub fn establish_connection() -> PgConnection {
    dotenv().ok();

    let database_url = env::var("DATABASE_URL")
        .expect("DATABASE_URL must be set");
    PgConnection::establish(&database_url)
        .expect(&format!("Error connecting to {}", database_url))
}

#[get("/books")]
fn books() -> Json<JsonValue> {
    use crate::schema::books::dsl::*;

    let connection = establish_connection();
    let results = books
        .limit(10)
        .load::<Book>(&connection)
        .expect("Error loading books");

    let mut books_json = json![];
    for book in results {
        books_json.push(json!({
            "title": book.title,
            "author": book.author,
            "publisher": book.publisher,
            "year": book.year,
        }));
    }

    Json(json!({ "books": books_json }))
}

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

In this example, the main.rs file sets up the Rocket web framework and exposes a single endpoint at /books that returns a JSON array of books. The establish_connection function sets up a connection to the PostgreSQL database using the DATABASE_URL environment variable. The books function uses Diesel to query the books table and return a JSON response to the client.


## Creating a Dockerfile

You will use the following Dockerfile to create a Docker image for the Rust application:

```
# Use an existing Rust image as the base
FROM rust:latest

# Set the working directory
WORKDIR /app

# Copy the application files into the image
COPY . .

# Build the application in release mode
RUN cargo build --release

# Set the command to run the binary
CMD ["./target/release/book-api"]
```

## Create a docker-compose.yml file

In the docker-compose.yml file, you'll define two services: one for the Rust application and one for the database:


```
version: '3'
services:
  book-api:
    build: .
    ports:
      - "3000:3000"

  db:
    image: postgres
    environment:
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
      POSTGRES_DB: book_db
```

## Starting the container service

Finally, you can start the containers by running the following command:

```
$ docker-compose up -d --build
```

With this setup, Docker Compose will start a container for the Rust application and a container for the PostgreSQL database, and connect the two containers so that the Rust application can access the database. Clients can access the REST API by sending HTTP requests to `http://localhost:3000`.