What do you use to debug Rust (particularly in Emacs?

Hi all!

So I'm trying to learn Rust for fun and I'm quite enjoying it. I encountered my first bugs, and I know that the debugger has to become a good friend of you to become proficient in a language. I apologize for the noobish question, but it's not clear to me:

  • as of 2023, what is the de facto Rust debugger? I'd say LLDB, since rustc uses LLVM, but I heard some old-ish posts about the debugger support being incomplete, using GDB, or rust-gdb. Is this still true?

  • In Emacs, what do you use to debug your code? dap-mode? If yes, how did you configure it? Or maybe realgud? Pure terminal debugging?

Thanks!

Over many decades I have rarely found I had to use a debugger in C or C++. On the rare occasions bugs showed up that were "difficult" I often found using a debugger did not help.

In Rust I have never felt the need for a debugger. Firstly because Rust does not allow one to create all those weird type and memory misuse errors that language like C/C++ suffer from. Secondly because it's so easy to integrate tests into ones development.

Use of a logger means judicious placement of info!() and debug!() statements will often pin point problems.

Finally when Rust throws a panic that pin points problems.

2 Likes

Personally I prefer using rust-lldb, which I believe used to be a wrapper around lldb that added a few rust-related helpers, but nowadays looks to be its own lldb binary.

I have seen other use rust-gdb and be happy with it.

But when you ask what the defacto rust debugger is, then I'm not sure what to reply. My gut feeling is that a lot of people who debug Rust code use CodeLLDB in Visual Studio Code -- but that's purely anecdotal. Personally I found configuring debug profiles is VSCode to be a little fiddly, so I just stick with rust-lldb.

I have had to do some remote debugging of Windows services written in Rust, and used Visual Studio together with msvsmon, and found this to be a mostly smooth experience (there's a crate called "verboten" that will help install msvsmon as a service in a maximally dangerous-and-helpful mode).

I will note that for the longest time, I saw no point in using a debugger with Rust (just as @ZiCog notes), but I have encountered a few situations where a debugger was very helpful. But it happens so rarely that each time I have to remind myself how to set things up.

1 Like

It is still a wrapper:

$ cat ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/rust-lldb
#!/bin/sh

[...]

# Call LLDB with the commands added to the argument list
exec "$lldb" --one-line-before-file "$script_import" --source-before-file "$commands_file" "$@"

Indeed it is!

I ran file $(which rust-lldb) and it gave me Mach-O 64-bit executable x86_64 but I see now that this is under ~/cargo/bin. Not sure how that got there, or if it is really intended to be before (in PATH) the toolchain script.

Who told you that?

Most of the things you'd normally need a debugger for in other languages (e.g. null pointer exceptions or segfaults) are already handled by the type system, so 9 times out of 10, if your code compiles then it's probably mostly correct. For the remaining 1 time, I'll write unit tests or use logging to make sure the code's logic works as expected.

I've been writing Rust for about 6 years now, and in all that time I've probably used a debugger 10 times, and it might have actually solved my problem 3 or 4 times... and this is coming from someone who wrote C# professionally for 3 or 4 years and would be stepping through code in the Visual Studios debugger every day.

In my opinion, the best debugging tool you can use is the Rust compiler itself.

6 Likes

That is a symlink to rustup just like ~/.cargo/bin/rustc and the like. It allows rustup to forward your invocation to the active toolchain. So for example ~/.cargo/bin/rust-lldb would call ~/.rustup/toolchains/stable-x86_64-apple-darwin/bin/rust-lldb if you are using stable rustc for the current project.

1 Like

Thanks to all for your answers!

Good suggestion, but what if I wanted to watch a variable for reassignments? I can do that in a debugger, but with logging I would need to find all the assignments in the code.

No one specifically, it's just my mindset about programming. Admittedly, I come from the usual C/C++/Java background, so I tend to think imperatively, and I am used to "follow" an execution to find a bug.

I see that, interesting, thanks! As a side question, how does rustup know which program to invoke?

rustup uses overrides as documented in Overrides - The rustup book (rust-lang.github.io) And more specifically, you can just ask rustup which executable it will use:

$ rustup which rustc
/home/jay/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rustc

$ rustup which cargo
/home/jay/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/cargo

$ rustup +stable which cargo
/home/jay/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/cargo

I am debugging my main programmes happily in emacs (See below). My problem is I cannot connect the debugger to tests.

On the rare occasions I cannot see immediately why a test is failing I would like to run the test in a symbolic debugger.

@ZiCog, and @Michael-F-Bryan whilst you two may not find symbolic debuggers useful, I do. I started programming a long time ago inside symbolic debuggers, and to this day my first job in a new IDE/language is to establish how to use the symbolic debugger.

I think it is unhelpful to intimate to people starting out in Rust that somehow there is something wrong with using them. Many of us, with deep experience, find symbolic debuggers extremely useful.

That said:

I use rust-gdb. I am unfamiliar with rust-lldb.

I find rust-gdb combined with:

  • rust-anlyzer
  • rustic
  • lsp-mode
  • lsp-ui-mode
  • company
  • flycheck
  • yasnippet.
  • hs-minor-mode

Hat tip

Works well for me except for my problem debugging tests. I really need a way to point gdb at the start of a test

3 Likes

I don't think anyone is saying there's something wrong with using them. But it's a pretty common observation among developers coming from languages like C/C++ that the need for a debugger is highly diminished when moving to Rust.

I'm sure there may be some personality traits shining through here; there's some spectrum of people who either see debuggers as literally debuggers, almost a necessary evil, and people who see debuggers as useful tools to inspect the detailed behavior of code, not isolated to bug hunting.

Your point is well taken. We shouldn't assume everyone views debuggers the same way. And we should be careful not to sound dismissive when people ask about debuggers.

2 Likes

I use all of the packages you use, except for company (switched to corfu) and hs-minor-mode. Actually using rust-gdb is not "good", but at least is decent. Moreover, since it is just a wrapper around GDB, I'd say that should be possible to integrate with RealGUD (I still have problems in using dap-mode even for the simplest program).

However (and that is a question open to everyone) I have an [Option] array in a Rust program, with values mixed between the two variants Some(i32) and None. Are you able to view it from the debuggers? Because apparently rust-gdb panics if I try to print it! And rust-lldb simply prints an empty array. Here:

  • source file (snippet):
fn main() {
    let v = [Some(1), Some(2), None];
    dbg!(v);
}
  • rust-gdb trace:
// GDB initialization
(gdb) b main.rs:14
Breakpoint 1 at 0x875f: file src/main.rs, line 14.
(gdb) r
Starting program: /tmp/rust-gdb/target/debug/rust-gdb 

This GDB supports auto-downloading debuginfo from the following URLs:
https://debuginfod.archlinux.org 
Enable debuginfod for this session? (y or [n]) 
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".

Breakpoint 1, rust_gdb::main () at src/main.rs:14
14	    dbg!(v);
(gdb) p v
$1 = [core::option::Option<i32>::Some(
../../gdb/gdbtypes.h:1064: internal-error: field: Assertion `idx >= 0 && idx < num_fields ()' failed.
A problem internal to GDB has been detected,
further debugging may prove unreliable.
----- Backtrace -----
0x557961575b0b ???
0x5579618b3cb4 ???
0x557961976c23 ???
0x5579614f5ed1 ???
0x5579618c5d93 ???
0x5579617f942c ???
0x5579618c104b ???
0x5579618c68a5 ???
0x5579618c7adb ???
0x5579617f901b ???
0x5579618c104b ???
0x5579615995c8 ???
0x5579618c2926 ???
0x557961784310 ???
0x557961785fc2 ???
0x5579615abd84 ???
0x55796187b782 ???
0x557961671bac ???
0x557961671c4d ???
0x557961662fcf ???
0x7f6a8543a246 ???
0x5579616669eb ???
0x557961666bd3 ???
0x557961671adf ???
0x557961977895 ???
0x557961977c69 ???
0x557961735c74 ???
0x5579614e1c14 ???
0x7f6a8471528f ???
0x7f6a84715349 ???
0x5579614e81f4 ???
0xffffffffffffffff ???
---------------------
../../gdb/gdbtypes.h:1064: internal-error: field: Assertion `idx >= 0 && idx < num_fields ()' failed.
A problem internal to GDB has been detected,
further debugging may prove unreliable.
Quit this debugging session? (y or n) 
  • rust-lldb trace:
// LLDB initialization

(lldb) target create "./rust-gdb"
Current executable set to '/tmp/rust-gdb/target/debug/rust-gdb' (x86_64).
(lldb) b main.rs:14
Breakpoint 1: 3 locations.
(lldb) r
Process 6127 launched: '/tmp/rust-gdb/target/debug/rust-gdb' (x86_64)
Process 6127 stopped
* thread #1, name = 'rust-gdb', stop reason = breakpoint 1.1
    frame #0: 0x000055555555c75f rust-gdb`rust_gdb::main::hf28a340875d6686e at main.rs:14:5
   11  	    let v = [Some(1), Some(2), None];
   12  	    
   13  	
-> 14  	    dbg!(v);
   15  	}
(lldb) p v
(core::option::Option<>[3]) $0 = {
  [0] =
  [1] =
  [2] =
}
(lldb) 

Is this a bug on the Rust side? Or a GDB one? Is the LLDB script flawed? I'm losing my mind after this thing, it really annoys me :smiling_face_with_tear:


EDIT: since this may be relevant but OT, I'm moving it to a new thread