Debugging Rust in Emacs with dap-mode

Hello,

The goal of this short how-to article is to summarize the various configurations and efforts I had to make in order to reach a debugging setup I like using Emacs.

We are going to use this small demo repository for this purpose. As you can see in its README, I was able to produce a decent debugging experience (similar to the very good gdb-many-windows setup that is built-in for C-like languages)

Figure 1: Debugging session for Rust using dap-mode (with dap-cpptools)

Figure 1: Debugging session for Rust using dap-mode (with dap-cpptools)

You can access the full picture there

Short version

  • Install lsp-mode, dap-mode, gdb, and rust-analyzer

  • Set Emacs up with the correct configuration. I used (roughly)

      (setq dap-cpptools-extension-version "1.5.1")
    
      (with-eval-after-load 'lsp-rust
        (require 'dap-cpptools))
    
      (with-eval-after-load 'dap-cpptools
        ;; Add a template specific for debugging Rust programs.
        ;; It is used for new projects, where I can M-x dap-edit-debug-template
        (dap-register-debug-template "Rust::CppTools Run Configuration"
                                     (list :type "cppdbg"
                                           :request "launch"
                                           :name "Rust::Run"
                                           :MIMode "gdb"
                                           :miDebuggerPath "rust-gdb"
                                           :environment []
                                           :program "${workspaceFolder}/target/debug/hello / replace with binary"
                                           :cwd "${workspaceFolder}"
                                           :console "external"
                                           :dap-compilation "cargo build"
                                           :dap-compilation-dir "${workspaceFolder}")))
    
      (with-eval-after-load 'dap-mode
        (setq dap-default-terminal-kind "integrated") ;; Make sure that terminal programs open a term for I/O in an Emacs buffer
        (dap-auto-configure-mode +1))
    
  • M-x dap-cpptools-setup

  • Then you can open a project and M-x dap-edit-debug-template to set up the template

What is Debug Adapter Protocol ?

The Debug Adapter Protocol (DAP) is a cousin of LSP, that describes how a client (Emacs here) can communicate with a debugger (rust-gdb here) to be controlled and send diagnostics.

Emacs has a DAP client with dap-mode, which is very closely coupled to lsp-mode. If you want to use dap-mode, I think it’s better to use lsp-mode over eglot as LSP client, if only because lsp-mode will be able to provide a simple one click Debug Test option when you use M-x lsp-lens-show.

So basically, you can imagine that the goal of DAP and dap-mode is to provide for all languages/all debuggers the same amazing experience as with gdb and gdb-many-windows.

How to test this dap-mode with Rust ?

Clone repo

You can find a small test repository on my github, that shows a minimal example of what to provide in a launch.json file to be able to start debugging.

Install dependencies

You will need 2 Emacs packages, at least 2 binary dependencies, and internet.

  • lsp-mode is an Emacs package that provides Run Test|Debug click buttons over tests
  • dap-mode is an Emacs package that provides a client implementation to DAP.
  • rust-gdb (or just gdb) is the debugger that will provide the DAP-compliant information to Emacs. The rust-gdb version has more visualization primitives for Rust data types
  • rust-analyzer is a LSP server, that will provide Emacs a way to detect tests and start debugging sessions.

After doing all that, you should use M-x dap-cpptools-setup in order to download the correct dependencies. They are coming from Microsoft, and you can choose which version you will use through the custom variable in Emacs.

Start a normal run debugging session

When you open the src/main.rs file in Emacs, and start lsp, you should see that rust-analyzer analyzes the repo to provide extra data. You can then (require 'dap-cpptools), in order to get access to the M-x dap-debug command.

dap-debug prompts you with multiple options/templates it knows. As I already included a launch.json file at the root of the repository, you should be able to see 2 preconfigured debugging setups to start:

  • Run Part A Debug (input.txt)
  • Run Part A Release (input.txt)

Feel free to look at the json file to see the various customization options, it’s the easiest way to learn how to configure a debug target. The documentation for the keys is available on Microsoft website.

After choosing the Debug run for example, Emacs will automatically:

  • compile the binary
  • set a breakpoint at entry (that doesn’t always work it seems)
  • run the program and open all auxiliary windows on breakpoint

Start a test debugging session

In order to start the tests, you only need to:

  • make sure the lenses are active with M-x lsp-lens-show,
  • it will show “Run Test | Debug” buttons near each test,
  • click the “Debug” button to start a debugging run of the test automatically.

First, if the program doesn’t stop, you can manually go in the src/main.rs buffer and use M-x dap-breakpoint-add, before running dap-debug again.

Once you’re stopped, you’re free to set additional breakpoints, or add variable watchers with M-x dap-ui-expressions-add. There is also a hydra to help with usual debugger commands : M-x dap-hydra

Conclusion

Hopefully all those steps worked without issue, and now you’re able to debug Rust programs in Emacs using a nicer interface and “classic” debugger features.

This blog post is part of a larger effort of mine to try to fully leverage LSP for work, and being able to move away from “eprintln!() debugging” and having more debugging/specific test running capabilities within Emacs was important for me. Also, hopefully this small test project will be able to be included somehow as an example in a WIP that will enable to spawn small test sessions from anywhere, so that demo-ing Emacs capabilities on “real” projects is easier to achieve.

I did not play a lot with this setup now (mostly because navigating RSeven scheme slots is really not ergonomic with all the nested Rc::RefCell), so there might be other quirks to deal with before the setup is really good; but I want this post to be a good starting point.

Have fun,

Gerry

Acknowledgements

It took me a while to figure things out, and it wouldn’t be possible without extra resources:

Doom Emacs

If the snippets in the article don’t work as is, it’s probably because I adapted them from my Doom Emacs configuration. The good news is that there shouldn’t a lot of Doom specific magic in the snippets, so hopefully the errors are easy to fix.

If you are using Doom and want to see what it looks like, you’re free to search through my configuration on SourceHut

 Share!