In this tutorial we will see how to install a complete [Rust](https://www.rust-lang.org) environment.
This will be useful for your own Rust projects, or to contribute to Durs, or to do NodeJS/Rust binding.
## Stable toolchain installation
Install Rust's stable toolchain :
curl https://sh.rustup.rs -sSf | sh
Add ~/.cargo/bin to your PATH environment variable :
export PATH="$HOME/.cargo/bin:$PATH"
I strongly recommend that you add this line to your terminal configuration file so that you don't have to copy it every time, if you don't know what I'm talking about then you most probably use the default shell (bash) and the file to which you need to add this line is `~/.bashrc`.
You will also need an integrated development environment, I recommend Visual Studio Code because it supports both NodeJs and Rust :)
You can find instructions on how to install vscode for your system on the Internet.
You can also develop in Rust with the following IDE/editors :
* IntelliJ Rust
* Eclipse/Corrosion
* Emacs
* VIM/Rust.vim
* Geany
* Neovim
And many others..
## Fmt : le formateur de code
I strongly recommend that you install the essential automatic code formatter, especially since it is maintained by the official Rust language team so you have the guarantee that your code will always compile (and will always have the same behavior) after the formatter's pass.
To install it you will need the toolchain nightly :
To automatically format your code, go to the root of your project and execute the following command :
cargo +nightly fmt
I strongly recommend that you create an alias in your shell configuration (~/.bashrc if you use bash). As an example I created the alias `fmt="cargo +nightly fmt"`.
## Clippy: the rust linter
If you contribute to Duniter's Rust implementation you will also need to use the Clippy linter. And in any case it's strongly recommended to beginners in Rust to use it, indeed clippy is very educational and will help you learn a lot how to code in Rust.
There are two ways to install clippy :
1. Compile it locally : it's long but it will run faster, it poses a major problem: you have to recompile it in nightly each time you update the toolchain rust and it often happens that clippy no longer compiles after an update. I therefore strongly advise against this method.
2. Running Clippy in docker: this is the method I recommend and use, it makes the execution of clippy a little slower but still allows to have a functional clippy and not to have to recompile it with each update.
### Clippy : method 1
Run the following command :
cargo +nightly install clippy
Warning it's taking a long time, and you must wait until the compilation is finished before launching Clippy.
To launch clippy, go to the root of your project and execute the following command :
cargo +nightly clippy --all
Clippy will then inform you in a very educational way about everything that needs to be modified in your code to be more in the "rust spirit" (We say then that your code is more "rusty").
### Clippy method 2
You need to install docker on your development workstation. Then, go to the root of your project and execute the following command :
docker run --rm -v "$(pwd)":/app -w /app instrumentisto/clippy
`instrumentisto/clippy` is a docker image that is automatically discarded and republished each time Clippy is updated, the big advantage is that the image is republished only if Clippy has been successfully compiled, so you are always guaranteed to be able to run the latest functional version of Clippy.
Once vscode is installed we will need the following 3 plugins :
* BetterTOML
* CodeLLDB
* Rust (rls)
An example of a `launch.conf` file for VSCode :
```json
{
"version":"0.2.0",
"configurations":[
{
"name":"Debug",
"type":"lldb",
"request":"launch",
"program":"${workspaceFolder}/target/debug/durs",
"cwd":"${workspaceRoot}",
"terminal":"integrated",
"args":["start"],
"env":{
"RUST_BACKTRACE":"1"
}
}
]
}
```
## RLS et LLDB
There is still to install RLS (Rust Language Server) and LLDB (debugger), the first one allows you to compile your code on the fly to highlight errors in red directly in your IDE/Editor, the second one is a debugger.
Although this is becoming increasingly rare, some rust crates still depend on C/C++ libraries and these must be installed on your computer at compile time. On Debian and derivatives, you must have `pkg-config` installed because the rust compiler uses it to find the C/C++ libraries installed on your system.
sudo apt-get install pkg-config
In the case of Durs you will need the openssl library for developers :
sudo apt-get install libssl-dev
This dependency on the ssl lib is optional, you can still compile Durs without it provided you disable the default features :
cargo build --no-default-features
## Test your environment with a traditional "Hello, World!"
mkdir hello-world
cd hello-world
cargo init --bin
The `--bin' option indicates that you want to create a binary, by default cargo create a library project.
You should have the following content in the `hello-world` folder:
$ tree
.
├── Cargo.toml
├── src
│ └── main.rs
This is the minimum content of any binary project, the source code is found in `main.rs`.
Any Rust project (binary or library) must contain a file named `Cargo.toml` at the root of the project, it is somehow the equivalent of the `package.json` of npm.
The `main.rs` file already contains by default a code to perform the traditional "Hello, world ! :
fn main() {
println!("Hello, world!");
}
Cette syntaxe doit vous rappeler furieusement le C/C++ pour ceux qui connaissent, et c'est bien normal car Rust est conçu pour être l'un des successeurs potentiel du C++. On peut toutefois déjà noter trois différences majeures avec le C/C++ :
This syntax must remind you furiously of C/C++ for those who know it, and that's normal because Rust is designed to be one of the potential successors of C++. However, three major differences can already be noted with C/C++ :
1. The main() function does not take any input parameters. Command line arguments are captured in a different way using the standard library.
2. println! is not a function, it's a macro. In Rust all macros are of the form `macro_name!(params)`, so it is to `!` that they are recognized. So why a macro just to print a string? Well because in Rust any function must have a finite number of parameters and each parameter must have an explicitly defined type. To exceed this limit we use a macro that will create the desired function during compilation.
3. The main() function doesn't return any value, when your program ends, Rust sends by default the EXIT_SUCCESS code to the OS. To interrupt your program by sending another exit code, there are macro such as `panic!(err_message)`.
Before changing the code, make sure that the default code compiles correctly :
Finished dev [unoptimized + debuginfo] target(s) in 0.91 secs
Cargo is the equivalent of npm for Rust, it will look for all the dependencies of the crates (=libraries) you install. Yes in Rust we speak of crates to designate an addiction, it can be a library or a package.
If you get a `Finished dev[unoptimized + debuginfo] target(s) in x.xx secs`, congratulations you just compiled your first Rust program :)
If you get an error it's because your Rust environment is not correctly installed, in this case I invite you to uninstall everything and restart this tutorial from scratch.
> It compiles for me, How do I run my program now ?
Like that :
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target/debug/hello-world`
Hello, world!
As indicated, `cargo run` executes your binary which is actually in `target/debug/`.
There are several compilation profiles, and you can even create your own, two pre-configured profiles are to be known absolutely :
1. The `debug` profile: this is the default profile, the compiler does not perform any optimization and integrates into the binary the entry points allowing a debugger to work.
2. The `release` profile: the compiler performs as much optimization as possible and does not integrate any entry point for the debugger.
Rust is known to be very fast, this is largely due to the extensive optimizations made during a `release` compilation, but making these optimizations takes time, so the `release` compilation is much longer than the `debug` mode.
To compile in `release` mode:
cargo build --release
Your final binary is then in `target/release/`.
To go further, the reference of the references you must absolutely read is obviously the sacred [Rust Book](https://doc.rust-lang.org/book/).
Dans ce tutoriel nous allons voir comment installer un environnement [Rust](https://www.rust-lang.org) complet.
Cela vous servira pour vos propres projets Rust, ou pour contribuer a Duniter-rs, ou pour faire du binding NodeJS-Rust.
## Installation de la toolchain stable
Installez la toolchain stable de Rust :
curl https://sh.rustup.rs -sSf | sh
Ajoutez ~/.cargo/bin a votre variable d'environnement PATH :
export PATH="$HOME/.cargo/bin:$PATH"
Je vous recommande vivement d'ajouter cette ligne dans le fichier de configuration de votre terminal pour ne pas avoir a la recopier a chaque fois, si vous ne savez pas de quoi je parle alors vous utilisez très probablement le shell par défaut (bash) et le fichier auquel vous devez ajouter cette ligne est `~/.bashrc`
Vous aurez aussi besoin d'un environnement de développement intégré, je vous recommande vscode car il supporte a la fois NodeJs et Rust :)
Vous trouverez les instructions d'installation de vscode pour votre système sur internet.
## Fmt : le formateur de code
Je vous recommande vivement d'installer l'indispensable formateur automatique de code, d'autant qu'il est maintenue par l'équipe officielle du langage Rust donc vous avez la garantie que votre code compilera toujours (et aura toujours le même comportement) après le passage du formateur.
Pour l'installer vous aurez besoin de la toolchain nightly:
Pour formater automatiquement votre code, placez vous a la racine de votre projet et éxécutez la commande suivante :
cargo +nightly fmt
Je vous recommande fortement de créer un alias dans la configuration de votre shell (~/.bashrc si vous utilisez bash). a titre d'exemple j'ai créer l'alias `fmt="cargo +nightly fmt"`.
## Clippy : le linteur
Si vous contribuez à l'implémentation Rust de Duniter vous devrez également utiliser le linteur Clippy. Et dans tout les cas il est vivement recommandé aux débutants en Rust de l'utiliser, en effet clippy est très pédagogique et vas beaucoup vous aider a apprendre comment il conviens de coder en Rust.
Il y a deux façons d'installer clippy :
1. Le compiler en local : c'est long mais il s'éxécutera plus vite, ça pose cependant un problème majeur : il faut le recompiler en nightly a chaque mise a jours de la toolchain rust et il arrive fréquemment que clippy ne compile plus après une mise à jours. Je déconseilel donc fortemetn cette méthode.
2. Éxécuter Clippy dans docker : c'est la méthode que je préconise et que j'utilise, cela rend l'éxécution de clippy un peu plus lente mais permet d'avoir toujours un clippy fonctionnel et de ne pas a voir besoin de le recompiler a chaque mise à jours.
### Clippy : méthode 1
Éxécutez la commande suivante :
cargo +nightly install clippy
Attention c'est long, et vous devez impérativement attendre que la compilation soit terminée avant de lancer Clippy.
Pour lancer clippy, rendez-vous a la racine de votre projet puis éxécutez la commande suivante :
cargo +nightly clippy --all
Clippy vas alors vous signaler de façopn très pédagogique tout ce qu'il conviens de modifier dans votre code pour être plus dans "l'esprit rust".
### Clippy méthode 2
Il vous faut installer docker sur votre poste de développement. Ensuite, rendez-vous a la racine de votre projet puis éxécutez la commande suivante :
docker run --rm -v "$(pwd)":/app -w /app instrumentisto/clippy
`instrumentisto/clippy` est une image docker qui est automatiquement rebuiltée et republiée à chaque mise a jours de Clippy, le gros avantage c'est que l'image n'est republiée que si Clippy s'est compilé avec succès, vous avez donc la garantie de toujours pouvoir éxécuter la dernière version fonctionnelle de clippy.
## Vscode
Rust étant un langage très récent, il n'a pas d'Environnement de Développement Intégré (IDE) dédié.
Heureusement, plusieurs IDE existants intègrent Rust via des plugins, nous vous recommandons vscode.
Il reste encore a installer RLS (Rust Language Server) et LLDB (debugger), le 1er permet de compiler votre code à la volée pour souligner en rouge les erreurs directement dans l’éditeur de vscode, le second est un débogueur.
Instructions d'installation de LLDB : https://github.com/vadimcn/vscode-lldb/wiki/Installing-on-Linux
Ensuite relancez vscode (après avoir installer les plugins indiquez si dessus), il devrait vous proposer spontanément d'installer RLS, dites oui.
Si cela échoue pour RLS, vous devrez l'installer manuellement avec la commande suivante :
## Paquets supplémentaires pour compiler duniter-rs
Bien que cela soit de plus en plus rare, certaines crates rust dépendent encore de bibliothèques C/C++ et celles-ci doivent être installer sur votre ordinateur lors de la compilation. Sous Debian et dérivés, vous devez avoir `pkg-config` d'installé car le compilateur rust s'en sert pour trouver les bibliothèques C/C++ installés sur votre système.
sudo apt-get install pkg-config
Dans le cas de Duniter-rs vous aurez besoin de la bibliothèque openssl pour développeurs :
sudo apt-get install libssl-dev
En réalité openssl est facultatif, vous pouvez compielr Durs sans en désactivant les features optionnelles :
cargo build --no-default-features
## Tester son environnement avec un "Hello, World !"
mkdir hello-world
cd hello-world
cargo init --bin
L'option `--bin` indique que vous souhaitez créer un binaire, par défaut c'est une bibliothèque qui sera créée.
Vous devriez avoir le contenu suivant dans le dossier `hello-world` :
$ tree
.
├── Cargo.toml
├── src
│ └── main.rs
C'est le contenu minimal de tout projets binaire, le code source ce trouve dans `main.rs`.
Tout projets Rust (binaire ou bibliothèque) doit contenir un fichier nommé Cargo.toml a la racine du projet, c'est on quelque sorte l'équivalent du `package.json` de NodeJs.
Le fichier `main.rs` contient déjà par défaut un code permettant de réaliser le traditionnel "Hello, world!" :
fn main() {
println!("Hello, world!");
}
Cette syntaxe doit vous rappeler furieusement le C/C++ pour ceux qui connaissent, et c'est bien normal car Rust est conçu pour être l'un des successeurs potentiel du C++. On peut toutefois déjà noter trois différences majeures avec le C/C++ :
1. La fonction main() ne prend aucun paramètre en entrée. Les arguments cli sont capturés d'une autre façon via une utilisation de la bibliothèque standard.
2. println! n'est pas une fonction, c'est une macro. En Rust toutes les macros sont de la forme `macro_name!(params)`, c'est donc au `!` qu'on les reconnaît. Alors pourquoi une macro juste pour printer une chaîne de caractères ? Et bien parce que en Rust toute fonction doit avoir a un nombre fini de paramètres et chaque paramètre doit avoir un type explicitement défini. Pour outrepasser cette limite on utilise une macro qui vas créer la fonction souhaitée lors de la compilation.
3. La fonction main() ne retourne aucune valeur, lorsque votre programme se termine, Rust envoi par défaut le code EXIT_SUCCESS a l'OS. Pour interrompre votre programme en envoyant un autre code de sortie, il existe des macro comme par exemple `panic!(err_message)`
Avant de modifier le code, assurez vous déjà que le code par défaut compile correctement :
Finished dev [unoptimized + debuginfo] target(s) in 0.91 secs
Cargo est l'équivalent de npm pour Rust, il vas chercher toutes les dépendances des crates (=bibliothèques) que vous installez. Oui en Rust on parle de crates pour désigner une dépendance, ça peut etre une bibliothèque ou un paquet.
Si vous obtenez bien un `Finished dev [unoptimized + debuginfo] target(s) in x.xx secs`, félicitations vous venez de compiler votre premier programme Rust :)
Si vous obtenez une erreur c'est que votre environnement Rust n'est pas correctement installé, dans ce cas je vous invite à tout désinstaller et à reprendre ce tutoriel de zéro.
> Chez moi ça compile, Comment j’exécute mon programme maintenant ?
Comme ça :
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target/debug/hello-world`
Hello, world!
Comme indiqué, cargo run exécute votre binaire qui se trouve en réalité dans `target/debug/`
Il existe plusieurs profils de compilation, et vous pouvez même créer les vôtres, deux profils pré-configuré sont a connaître absolument :
1. Le profil `debug` : c'est le profil par défaut, le compilateur n'effectue aucune optimisation et intègre au binaire les points d'entrée permettant à un débogueur de fonctionner.
2. Le profil `release` : le compilateur effectue le maximum d'optimisation possibles et n'intègre aucun point d'entrée pour le débogueur.
Rust est réputé pour être ultra-rapide, c'est en grande partie grâce aux optimisations poussés effectués lors d'une compilation en profil `release`, mais réaliser ces optimisations demande du temps, la compilation en mode `release` est donc bien plus longue qu'en mode `debug`.
Pour compiler en mode `release` :
cargo build --release
Votre binaire final se trouve alors dans `target/release/`.
Pour aller plus loin, je vous invite a lire l'excellent [tutoriel Rust de Guillaume Gomez](https://blog.guillaume-gomez.fr/Rust).
Et si vous savez lire l'anglais, la référence des références que vous devez absolument lire c'est évidemment le sacro-sain [Rust Book](https://doc.rust-lang.org/book/).
Le Rust Book par vraiment de zéro et se lit très facilement même avec un faible niveau en anglais.