Overview
This blog is my attempt at documenting what I've learnt on my journey using Rust. I have worked as a video game programmer for over 25 years and as a result have used C++ as my main language of choice, even for personal projects. Today, I will never touch C++ willingly again since discovering and learning about Rust. I will restrict C++'s use to my day job, and that's all. In my opinion, Rust has made C++ redundant for writing new software.
I have managed to hone my development environment for Rust programming over the last few months and I want to share my approach. This is what my first article is about - setting up a development environment for effective Rust programming.
Prerequisites
I will assume the reader knows their way around a terminal (or shell). That is, they know how to make folders, set the current folder, and launch commands all via their terminal.
I will also assume that the reader has prior programming experience, perhaps in C, C++ or similar language. These guides will not be teaching you how to program, but rather how to apply your current programming skills to a new language.
You will also need an internet connection. The Rust build system will constantly download packages (called crates) from the world wide web.
Installing Rust
The best way to install Rust is using the tool Rustup
. Windows' users will require downloading an executable, but for operating systems with more sane command shells, a single cut & paste will suffice.
Hop over to http://rustup.rs and follow the instructions. After running the executable rustup-init.exe
on Windows, or executing the shell command on other operating systems, you will see a screen-full of text and 3 options:
Go ahead and choose option 1 and you will have Rust and its associated tools installed. To make sure, open up your favourite terminal and type:
$ rustc --version
if everything went well (and I hope it does because this guide will not help you otherwise), you should see something like:
rustc 1.53.0 (53cb7b09b 2021-06-17)
The installation procedure involves rustup
downloading and installing "components". The components you might have seen being installed would be:
- cargo - the ultimate, bad-ass package/build/do-everything tool that will be the centre of your Rust-programming experience.
- clippy - this is a linting tool that can scan your code and make suggestions on how to improve it, or catch problems. This is an essential part of your workflow.
- rust-docs - a cargo plug-in that provides auto-generation of documentation from your source code. If you're familiar with Doxygen, it's similar to that.
- rustc - the compiler, nuff said!
- rustfmt - Rust's very opinionated code formatter. It will auto-format your code according to a set of standards so that everyone's Rust code looks the same. This can be annoying at first but you reap the benefits if you stick with it.
Your first Rust program
Let's try the obligatory Hello World program. And because of Cargo, you can do this without typing a line of code! Most guides, will demonstrate how to use rustc
but you do not need to touch this directly. You will use Cargo to check, build, test, benchmark and run your code as well as many other things. So jump into your favourite terminal, cd
to your folder you've set aside for Rust development (I use Windows and so my folder of choice will be r:\
) and type:
$ cargo new hello
$ cd hello
You will get a message back telling you that you've created a binary package called 'hello'.
Let's look inside your folder that you've just created. First, you will see a Cargo.toml
file. This gives information to Cargo on how to build your project and other meta-data (like authorship, version etc). We'll cover this later.
Secondly, there is a hidden file and directory related to git
, which is the default source control system used by most Rust programmers. You can switch it to Mercurial or something else. To read about that, rush over to https://doc.rust-lang.org/cargo/commands/cargo-new.html and checkout the --vcs
command line option. I will be ignoring this file and folder in this guide.
Finally, there is a src
folder, and that is where all your rust source code lies. If you look inside, you will see a lonely file called main.rs
. Let's view it. You can use type
(Windows) or cat
(other operating systems) to show the contents of src/main.rs
:
fn main() {
println!("Hello, world!");
}
The hello world program has already been written, courtesy of Cargo. Let's examine the code:
-
fn marks the start of a function and in this case defines the function called
main
. All Rust applications start at this function, like C/C++ does. If you build with Cargo, which we will, there also has to be amain.rs
file in the root of thesrc
folder. The parentheses following it usually contain parameters (we have none right now), and the code belonging to that function is in between the braces. If you are used to C, C++, C#, JavaScript etc, all this will be familiar to you. -
println! will print text to standard output. The exclamation mark in the name indicates that
println!
is not a function but a macro. As in Lisp macros, not the useless C style macros. Rust can generate code during compilation. In this case,println!
will expand to many statements, depending on its arguments, that will be inserted at the point of the call.
OK, let's run it. Type:
$ cargo run
Compiling hello v0.1.0 (R:\hello)
Finished dev [unoptimized + debuginfo] target(s) in 1.11s
Running `target\debug\hello.exe`
Hello, world!
This will compile the code and run it. The astute of you will have noticed a new folder appearing called target
. Dig deeper and inside that you will see a debug
folder. Finally, inside that you will see the hello.exe
you just built and that Cargo just ran.
In fact, the compiler output before the 'Hello, world!' message describes the type of application you just built (unoptimised and including debug information) and where to find it.
We can build and run an optimised one too:
$ cargo run --release
Compiling hello v0.1.0 (R:\hello)
Finished release [optimized] target(s) in 0.66s
Running `target\release\hello.exe`
Hello, world!
Here are some other Cargo commands worth knowing to begin with:
-
cargo build
- this will just build the final executable.cargo run
will call this implicitly if it needs to. -
cargo clean
- this will delete thetarget
folder and all the generated files within. -
cargo check
- useful while writing code. This will compile your code but without generating the code. This is a lot faster thancargo build
and is useful if you just want to check for syntax errors. Because of the excellent IDE integration that I will demonstrate later, this is rarely needed.
One more point, after building your executables, a Cargo.lock
file appeared too. This file is generated to lock down versions of other packages you might have used to build your program. This allows the Rust compiler to recreate the exact executable after a clean. It ensures that the same versions of packages are downloaded, built and linked with your application as before. For now, we don't use packages so this file contains very little information. If you use source control (like Git), you should include this file during submission, along with Cargo.toml
and your source code.
The Editor
The second step in starting to learn Rust is setting up your programming environment just right so as to minimise friction between you and your work. The main part of that decision is which editor to use. I've tried writing Rust code in Emacs, Vim, Sublime but the text editor I eventually settled on is Visual Studio Code
. For me, it's the best product Microsoft have ever created.
Why Visual Studio Code? In my opinion, it has the most mature tooling for writing Rust code, and for interacting with Rust's ecosystem and build tools (i.e. Cargo).
So go over to https://code.visualstudio.com/ and download and install it.
Restart your terminal, and enter:
$ code .
And lo and behold:
The terminal is nice and all but there's all that typing. Don't we do enough while coding? So let's see if we can build from Visual Studio Code (hereafter referred to as VS Code). Currently, it can't because it needs extensions.
VS Code Extensions
To turn VS Code into a powerhouse editor of Rust, we need to install some extensions. By clicking on the 5th icon on the left (it looks like 4 squares, with one of them offset) you open up the extensions interface. Use the search box to find the necessary extensions listed below, then click on the blue 'install' buttons beside the extension's entry:
- rust-analyzer - provides the 'intellisense' for Rust and many other Rust-related utilities.
-
C/C++ - what??? You need a C/C++ extension? Yes, it contains the debugger and it's useful for inserting breakpoints and stepping through your code. You can also use
CodeLLDB
but for me, theC/C++
one feels quicker.
Also, there are some useful extensions to install, that while unnecessary, are very useful all the same:
- crates - checks the versions of any crates you reference (more on that later).
-
Markdown All in One - you will be writing several markdown files (files with
.md
extensions). This extension makes it easier to do so and provides a useful preview mode. -
Even Better TOML - some editor support for editing TOML files such as
Cargo.TOML
. -
Rewrap - not related to Rust but provides the ability to reformat text (in markdown files and Rust comments) so that they fit on the lines better. By pressing
Alt+Q
, you can change a couple of long comments into several shorter ones that fit on the screen. As you will eventually see, Rust comments will take a big part in document generation.
If you clear the search box, you will see the extensions you've installed. Some buttons beside them may be labelled 'Reload Required', and if you do, press it to restart VS Code in a matter of seconds.
After installing rust-analyzer
, you will see a dialog box pop up in the bottom right corner of the window. If you miss it, click on the notifications
icon in the bottom-right of the window. It should look like this:
Click the 'Download now' button. If you don't see it, restart VS Code and it should show again. This will download the Rust Language Server (RLS), which is the external program that can analyse your source code and communicate back to VS Code, via the extension, to display useful information about your program, including auto-complete.
When downloaded, 'rust-analyzer' will get to work looking at your code. Open up src/main.rs
using VS Code and you will see two new links above the main
function:
Click on Run
and VS Code will build and run your program. You can see the results in the terminal window that automatically opens up.
Alternatively, you can click on Debug
to do the same thing. But right now VS Code isn't properly set up for debugging.
Debugging in VS Code
If you haven't used VS Code before, it probably isn't set up for debugging facilities like breakpoints. So press Ctrl+Comma
to open up VS Code's settings window. Type breakpoints
in the search bar at the top of this window, and make sure Debug: Allow Breakpoints Everywhere
is selected:
While you're in the settings window, retype the search parameter to be formatter
. Ensure Editor: Default Formatter
is set to rust-analyzer
and Editor: Format On Save
is checked. This will allow the rustfmt
component mentioned earlier to do its magic every time you save a file. It's a really nice time-saver. You can type code as messily as you want in the sweet knowledge that when you save, everything will move to its correct place, and missing trailing commas and spacing will be inserted.
Now you've done this, you can hit F9
while editing to add a breakpoint to the very line you're on. Try doing this now on the println!
statement in src/main.rs
:
Click on 'Debug' and see the code stop on the line before it executes it. Tap F10
to step over the next line and you will see 'Hello, world!' appear as it executes it. Press F5
to continue running, or Shift+F5
to stop debugging altogether. Don't forget to tap F9
again on the line with the breakpoint to remove it!
Launchers and Tasks in VS Code
These links in your code are very convenient, but it requires that you have src/main.rs
open at the main
function to see them. Wouldn't it be better if we could press a key and have it run or build?
First step is coercing VS Code to build your source code at a press of a key. Press F1
to bring up the command palette, which is an interface in VS Code that contains all the possible commands you can give to it. Now type build
and the command menu will whittle down to a few entries, one of which is Tasks: Configure Default Build Task
.
Select it. A new menu will appear with a list of possible Cargo commands. We want to choose rust: cargo build
. This will create a new file in your project at .vscode/tasks.json
. This file defines a task that VS Code can run. It calls cargo build
and that's all. But let's improve on it.
Firstly, change the label
entry to something shorter. It can be anything you want, but I usually change it to build
. It will be referenced by another file later.
Secondly, we would like the terminal window in VS Code to clear every time we start a build so we can clear distinguish current messages from a previous build's messages. Add a comma at the end of the label
line and then start typing presentation
. After a few characters you should see an intellisense window open up suggesting presentation
. Hit ENTER and VS Code will auto-complete the entry. Very handy! Now change the clear
entry so it says true
instead of false
. The final version should look like this:
{
"version": "2.0.0",
"tasks": [
{
"type": "cargo",
"command": "build",
"problemMatcher": [
"$rustc"
],
"group": {
"kind": "build",
"isDefault": true
},
"label": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": true
}
}
]
}
Now, select File > Preferences > Keyboard Shortcuts
from the menu and type build
in the search bar of the new keyboard shortcuts window.
Double-click under the Keybinding
column on the row that says Tasks: Run Build Task
. This will allow you to bind a key to that command. Type F7
followed by ENTER
and you're done. VS Code will warn you that other bindings use the same key, but ignore those as they will not conflict. I chose F7 because that's the usual key on the Visual Studio IDE for building and I am used to it. Of course, you can choose any other key combination that suits you.
Let's test it! Hit F7 now (or whichever key combination you chose) and the terminal window in VS Code will clear and start your build!
Now have it building at a press of a key, we want it to run (after building) at a press of another key. VS Code already has F5
set up for that task, but currently doesn't know how.
Click on the 4th icon on the left side of VS Code, the one that looks like a triangle with a bug on the corner. When you do this, you will see a new interface with a big Run and Debug
button (but don't press it!).
Under that button, you should see a link that says create a launch.json file
. To run your program at a press of a button, we need to create a launcher. Launchers are configured via this launch.json
file, so go ahead and click on that link. You will see a dialog asking you to select and environment.
Choose C++ (Windows)
if you're on Windows and you used the C/C++
extension, or choose C++ (GDB/LLDB)
otherwise. This will create a new file called .vscode/launch.json
. As before, let's make a few changes:
- Change the
program
value so it reads${workspaceFolder}/target/debug/hello.exe
. This needs to match the final executable you're running. - Change the
console
value so it readsintegratedTerminal
and add a comma at the end. - After the
console
entry, and the reason you added a comma, add the entry"preLaunchTask": "build"
. This is the line that references the label text you entered intasks.json
. They must match. This line tells the launcher to ensure that the build task is run first. We want to run the latest version of our program, don't we?
The final result will be:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(Windows) Launch",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/target/debug/hello.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"preLaunchTask": "build"
}
]
}
Press F5
and the launcher should run. You should see in your terminal, build messages and the output of the program.
Occasionally, I have seen VS Code struggle with this. If it doesn't run, check your tasks.json
and launch.json
files, and run cargo clean
from the terminal within your project folder.
Compiler Errors
This blog is getting a bit long now, so one last thing to talk about. What happens if you have errors in your code. The good news is that the rust-analyzer
extension and VS Code will conspire to help you out. Let's add an error in our code. After the println!
statement, add a new one:
fn main() {
println!("Hello, world!");
foo();
}
Then hit save (Ctrl+S
). rust-analyzer
will generally only see your changes after saving. Now the error is in the program, without building multiple things will happen:
- A red line will be visible under
foo
. Move your mouse pointer over it and you will see a popup describing the error. - Looking at the tab above your editing window, you will see that the filename has turned red with a number after it. (There is also a letter U but that's related to source control so ignore it). That number is how many errors there are. The tab text can turn yellow too, which means there are warnings but no errors. This time the number counts how many warnings there are. At a glance you can see how many errors in your program.
- On the scrollbar in your window, you will see a little red marker. This shows roughly where your error is.
- If your terminal window is still open in VS Code, you will see a number next to the
Problems
tab. Inside this tab, you will see a list of errors. Click on them to jump to the line of code with the error.
Wow, that's a lot of clues something has gone wrong with your program. Now hit your build key to run the build task (remember mine was F7
). In the Terminal
tab below you will see the output of the compiler. It usually has a better error message than seen elsewhere:
Rust provides the best error messages EVER! You can even go to your terminal and type rustc --explain E0765
, as described, to explain the error above in more detail. Truly amazing!
Summary
That was a lot to go over. But we installed Rust and it's tools, installed VS Code and extended it to support Rust, and then showed how to use VS Code to build and run your program.
Part #2 will talk about the borrow checker, ownership, lifetimes and all those nice things. See you then!
Top comments (2)
Thanks a lot, teacher. Quite complete and good dedication.
Well-written, concise, and easy-to-follow. The perfect trifecta. Thank you!