Debugging Rust Code in CLion
It’s been a while since we last dedicated a whole blog entry to IntelliJ Rust. In this post, we’ll take a closer look at how the plugin cooperates with CLion when it comes to debugging your Rust applications.
We’ll start by diving right into a debug session to get a grounding in the basics, and then we’ll explore the debugger settings and additional options in detail.
- Before you begin
- How to start debugging
- Switching debuggers and renderers
- Debugging a Cargo Command configuration
- More debug features
If you need more information at any point, please refer to the plugin’s Quick Start Guide and the Debug section in CLion’s web help.
Before you begin
First of all, make sure that your project is fully loaded. If the indexing is finished and the Cargo tool window shows all the modules and targets of the workspace, you’re good to go.
On Windows, go to Settings | Build, Execution, Deployment | Toolchain and set either Cygwin or MinGW as your working environment. Then, run rustup toolchain list
and check the first line: it should be one of the gnu versions matching the bitness of the debugger. For example, if you’re working on MinGW (32-bit), the default toolchain should be i686-pc-windows-gnu. If you’re on MinGW64, it should be x86_64-pc-windows-gnu. You can set the appropriate toolchain by running rustup default <toolchain_name>
.
How to start debugging
If we want to debug a test or target that doesn’t take input arguments, the quickest way is to use the gutter menu.
Let’s pick a test we want to debug, place a few breakpoints in the code, and click the gutter icon next to the test name:
The plugin will call cargo test
to get the non-optimized binary with debug information, which it will then launch under the debugger.
When our test hits the first breakpoint, the Debug window pops up automatically. It’s organized around the stack trace and threads: we can switch from one thread to another and travel up and down the frames.
On the right side, we have the variables in the current scope. We can search in this pane by typing right into it and expand the child nodes of the data structures.
Actually, we don’t have to dig into the Variables pane at every step, as the values are also shown inline, together with the variables:
Across the top of the Debug window, we have stepping buttons like Step Over (F8
), Step Into (F7
), and Force Step Into (Shift+Alt+F7
on Windows/Linux, ⇧⌥F7
on macOS). The latter is helpful when we want to get into a function whose source code is unavailable. In such cases, Force Step Into jumps right to disassembly:
Now that we know how to step through and examine variables, let’s stop the session and take a look at the settings.
Switching debuggers and renderers
We can pick the debugger that IntelliJ Rust will employ:
- On macOS and Linux, the options are bundled LLDB, bundled GDB, or a custom GDB binary.
- On Windows, we can choose between bundled GDB for MinGW, Cygwin GDB, or custom GDB.
The supported versions are listed in CLion’s web help.
Apart from the debugger itself, we can switch the renderers it will use when showing Rust types in the Variables pane:
No renderers: This option disables explicit rendering, leaving only the type support provided by GDB or LLDB natively. Because Rust type formatters are not available in the debuggers out of the box in their current state, this option means no rendering for types that differ from C/C++.
Rust compiler’s renderers: With this option, IntelliJ Rust takes pretty-printers from the standard rustc distribution and loads them onto the debugger.
Bundled renderers: The plugin’s own renderers were implemented from scratch for both GDB and LLDB, unifying types representation for the two debuggers. These formatters build tree views for strings, structs, enums, and vectors, making it easier for you to look inside child elements.
The bundled renderers also support standard library types like HashMap
, HashSet
, Rc
, Arc
, Cell
, Ref
, and others.
Debugging a Cargo Command configuration
Let’s get back to debugging, but this time let’s use a run/debug configuration.
In fact, when earlier we started a debug session from the gutter menu, CLion created a temporary Cargo Command configuration, which we can see greyed out in the switcher:
Temporary configurations are fully functional, but you can only have a limited number of them at a time. So if you debug many targets and tests using gutter menus, only the last few corresponding configurations will be available. You can always use Save… to make them permanent though.
To create another configuration based on the same template, go to Edit Configurations, click +, and then select Cargo Command.
Use the following pattern for the Command field:
[run]
or [test]
[build options] [--] [program arguments]
Notice the --
prefix followed by an extra space. It separates the input arguments from the build options that will be passed to Cargo.
When we debug this configuration, the plugin will start by calling cargo build [build options]
and then it will launch the binary under the debugger with [program arguments]
.
Now we can save this configuration. Make sure to select it in the switcher, and click Debug (Ctrl+D
on Windows/Linux, ⌃D
on macOS) to start a new session.
More debug features
Evaluate Expression (Alt+F8
on Windows/Linux, ⌥F8
on macOS) helps us calculate values on the run and monitor how the results are changing while we step.
Keep in mind though that the evaluation is performed by GDB or LLDB, which means the functionality is limited to what the particular debugger’s parser can provide (for GDB, check out the supported Rust features). Because of this, Evaluate Expression currently works only in simple cases like arithmetic and logic operations with possible access to structure elements and pointers. The same limitations apply to the expressions you can set for conditional breakpoints.
If we pick a pointer in the Variables pane and call Show in Memory View from the context menu, we will get into the raw memory of the running process. This action opens a window with a 256-byte memory region starting from the chosen address.
While we are stepping through the code, the Memory View highlights the changes happening in the current range:
If we want to access the GDB/LLDB console, there is no need to leave CLion: the debugger console is located next to the Variables tab. It shows the debugger’s output and lets us run commands with the completion assistance that GDB and LLDB provide for their command sets.
Another tiny but handy feature is the Hex View. It calculates hexadecimal values for integer variables and shows them alongside the original formatting (or instead of it).
When enabled, hex values are shown both in the Variables pane and inlined in the editor:
The Hex View is under development at the moment. If you want to turn it on, follow these instructions from the web help.
One more feature that can help us debug is a configuration setting called Backtrace. It lets you set the RUST_BACKTRACE environment variable, which prints the unwound stack trace into error messages and controls its length.
Although debuggers don’t suspend Rust programs on fatal errors (panic!
-s), Backtrace gives us the opportunity to investigate what happened by digging into the printed trace. Here is an example of how it looks when Backtrace is set to Full:
________________________
That’s about it for debugging Rust code today. Thanks for reading!
If you have any questions, feel free to ask them here in the comments or ping the IntelliJ Rust team in gitter.
We plan to publish a series of blog posts covering Rust development in CLion. Keep an eye on the blog, or subscribe by selecting the Send me Rust blog posts checkbox (scroll up and look on the right-hand side of the blog page).
Ready to give the debugging features a try? Click the button below to get CLion and then install the Rust plugin.
Your Rust Team
JetBrains
The Drive to Develop