Learn Rust — Part 1

Sebastien Dorgan
The Startup
Published in
7 min readNov 1, 2020

Introduction

It’s been a while now that I wanted to learn Rust and also to write a article on Medium so I decided to do both at the same time.

Although it is not my mother tongue, I wrote this article in English. Maybe there will be some mistakes or some weird turns of phrase, please be kind to me but do not hesitate to correct me, I’m only asking for improvement :).

In this article, I describe how to create a complete Rust development environment, usable from a browser and contained in a docker image. We are also going to write our first program and learn how to use the debugger.

For those who are very impatient, the link to the github repository is given in the conclusion.

Create a portable development environment

To begin we are going to create a Dockerfile containing all the necessary tools to program in Rust. This is how this Dockerfile looks like:

Although it’s not extremely complex, it took me longer than I expected to create this Dockerfile and I think it’s interesting that we go through it together.

The first part is very common. I chose ubuntu:20.04 as base image. Indeed ubuntu is widely adopted by the docker community thus it is easy to find support. And the version 20.04 because it is a long term support. The RUN directive installs some necessary system tools:

  • Curl to download some installation scripts and packages
  • Git because it is used by the Rust build system and package manager: Cargo
  • sudo because it is needed to install code-server, a very nice VS Code clone with a web UI
  • build-essential: a meta package containing essential building tools
  • LLDB: the debugger of the LLVM project

As it is a bad practice to create root containers, Iet’s create a user named rustdev. The default SHELL for this user is bash, the home directory is /home/rustdev , the password is disabled. To be able to install code-server, it is also needed to add rustdev to the sudoer list to allow rustdev to use sudo without password. To be perfectly honest, giving to a user the possibility to run sudo without password is also not a very good practice. This will be fixed later by removing the package sudo. The SHELL environment variable is also set to make bash the default shell.

The next section sets the user to be rustdev and the working directory to be /home/rustdev, the home directory of rustdev user. The USER environment variable is defined to be able to use Cargo later. If the USER environment variable is not defined when running cargo new … command in the container, an error is raised: “could not determine the current user, please set $USER”

Now its time to install , the Rust toolchain installer. The rustup.sh script installs executables like rustc and cargo in ~/.cargo/bin, that is why it is needed to add /home/rustdev/.cargo/bin to the PATH.

There is a bit more to say about the installation of code-server. Installing it requires you to be sudoer because the install script installs a deb package. I used standalone method to install code-server because it puts it in the ~/.local directory which allows rustdev user to install extension without sudo. Once the installation is completed, the ~/.local directory is owned by root user. I used the chown command to change the ownership to be rustdev. Finally /home/rustdev/.local/bin is added to the PATH as it contains the code-server executable.

Now that code-server is installed, it istime to add some extensions to make code-server user-friendly with Rust developers. I use rustup to install Rust toolchain components that are necessary for the extensions I want. If you don’t install these components now, this will be done each time you re-run the container which is not the behavior I wanted.

After reading a post by Thiago Massari Guedes I decided to trust him and to add rust, rust-analyzer and vscode-lldb extensions even if rust-analyzer is in alpha version and that the documentation of rust-analyzer says that it may cause conflicts with the official Rust plugin. We will see in use if Thiago’s recommendations were good, I will make a feedback in the next posts as I progress in the discovery of Rust.

Rust extension is straightforward to install using code-server command line tool, rust-analyzer and vscode-lldb were more problematic.

My first attempt to install rust-analyzer was to simply run code-server -install-extension matklad.rust-analyzer. The problem was it just installs the extension without downloading the rust-analyzer binary required by the extension. As a consequence, the executable is downloaded each time the container is run, when you try to use the extension for the first time. To prevent this behavior the rust-analyzer executable can be downloaded directly from github and put in the code-server global storage where the rust-analyzer extension expect to find it.

Unfortunately, even taking these precautions, when the container is run and the extension is activated by opening a first Rust project, the rust-analyzer extension ask for downloading the rust-analyzer binary. I have inspected the code of the rust-analyzer extension to understand the problem, if you are interested, I created an issue in github.

A fix could be to modify the package.json of the extension by removing the version, but it is very dirty so I prefer keep the Dockerfile as these and wait the rust-analyzer team solves the problem as it is non blocking.

For vscode-lldb extension I have also tried to install it the same way I have installed rust and rust-analyzer extensions but I encounter problems trying to use it. After some research I find the vscode-lldb issue 314 so I tried the fix proposed. The fix consists in downloading manually from github the vsix extension file and adding it using the code-server command line, it works perfectly.

After that, I ended by installing the better-toml extension useful to edit Cargo toml configuration files.

Now everything is installed, it is time to remove the sudo package that is no more necessary. This can be done only as root user. Once sudo package is removed the user can be set again to rustdev to run code-server.

To run code-server we need to overload the binding address and port to be able to access the web UI from the host. Because this container will be used locally, for testing purpose, we can also by-pass authentication mechanism.

To test if everything is working correctly simply use docker build in the directory where the Dockerfile is saved.

docker build -t rustcoder .

Run the docker image

This is how my working repository is organized:

├── docker-compose.yaml
├── docker-image
│ └── Dockerfile
├── projects
  • The file docker-compose.yaml is a docker compose file that defines how to build and run the image.
  • The directory docker-image contains the Dockerfile.
  • The directory projects is an empty repository.

I also need to precise that I use docker version 19.03.8 and docker-compose version 1.27.4.

Knowing that this is how my docker-compose.yaml looks like:

The file format version is 3.8 which is a version compatible with my version of docker and docker-compose.

version: "3.8"

The file defines only one service, rustcoder.

services:
rustcoder:

The image of the rustcoder service is built using the Dockerfile in the directory ./docker-image.

build: ./docker-image

The port 8080 of the container is binded to the port 8080 of host.

ports:
- 8080:8080

The capability SYS_PTRACE is added because LLDB uses ptrace system calls.

cap_add:
- SYS_PTRACE

To allow LLDB working correctly inside the container the seccomp security profile is set to unconfined which means that the container will be run without the default seccomp profile.

security_opt:
- seccomp:unconfined

The host directory ./projects is mounted into the container /home/rustdev/projects directory.

volumes:
- ./projects:/home/rustdev/projects

Now the rustcoder service can be run using docker-compose in the directory where the docker-compose.yaml file is saved.

docker-compose up

Once done, the Rust development environment is accessible from http://localhost:8080

Create a project with Cargo

Cargo is the main tool for any Rust developer, it is used to create, compile, test, package and distribute projects.

To create a project with Cargo we need first to open a Terminal inside code-server

Once the Terminal is opened go to projects directory and run

cargo new hello_world

Than click on Open Folder in the welcome screen or from the menu click File > Open Folder and then select /home/rustdev/projects/hello_world.

Due to the “bug” explained before the rust-analyzer extension should ask you for downloading the rust-analyzer binary.

Once done, open the /home/rustdev/projects/hello_world/main.rs file. Now you can run your first Rust program by simply clicking Run above the main function definition.

Debug your program with code-server

To illustrate how debugging work we will modify a little bit the main function by creating a greetings variable and putting a breakpoint by clicking in the margin (the red bullet).

To start the debugging session click on Debug, code-server automatically opens the debug panel and the execution is stopped to the breakpoint. You can now click on the step over icon (the blue rounded arrow with a point bellow) that appears in overlay.

Conclusion

We have now a complete Rust development environment and we are ready to start learning Rust :).

The code developed to create the post is available on github under CC0–1.0 License.

Originally published at https://do4tech.github.io/01-Learn-Rust/.

--

--