How does a Nim developer debug programs, with unmangled display of variables? The current values of relevant variables, with variable names as your code gives them, at (assuming non-optimized) the current line of code.
This question has, over the years, been repeatedly asked on this forum. I've read several such threads dating back to ~2018 - and attempted to work through roughly six of the repositories, guides, and even step-by-step walkthroughs mentioned therein.
I've failed every. single. time.
Right now, I'm on VS Code, on Windows. I've installed, and confirmed the functionality of:
Long story short, I'm in toolchain hell. More compiler, debugger, editor, build system, and multiple scripting and programming languages savvy is being asked of me than I've got.
Are you willing to share your setup, and willing also to be step-by-step about it?
Thank you for the responses. I've tried the suggestion to rely on printing out variables in a Nim setting, and I'm seeing similar limitations as I do in my C and C++ codebases.
When I was a young programmer, I relied exclusively on printing out variables. Over time, however, I used more and more code that:
In such cases, I discovered that trying to print out variables, starting with a mere guess as to the right value ID and the right code location, was an extraordinarily tedious way of finding the root cause of error or crashes.
More and more, I realized what a million hackers had already figured out before: If you're working with code the logic of which you don't know like the back of your hand, there is no productive substitute to - at a minimum! - step-by-step debugging and automatic display of variables.
Not the place you thought the error occurred at, but the perhaps multiple places the errors actually happened at. Not the variable that you think is the clue, manually chosen and typed out, but the multiple and interacting clues you actually need - and didn't realize you needed - automatically displayed if applicable in the local block. Not with your eyes on wherever you manually put the printout (and in some library or low-level code you may have trouble doing this), but with your eyes on the specific code lines where your data's getting borked.
Printouts, in a word, are a terribly slow integration to a solution - if, again, you aren't a revved-up, hands-on expert with the relevant code and dependencies.
Opening remarks following up on the above:
Nim, as a language, I find more readable than almost any other. It eschews unnecessary punctuation, avoids complex syntax for simple tasks, and has Standard Methods for a wide variety of purposes. I therefore find code written in Nim by others to be more easily understandable than in most other languages.
This has immense potential to advance Code Reuse: the ability of small outfits and individuals to complete with megacorps by making use of the work of many other open-source developers.
Nim, as a debugging experience, does not sufficiently follow through on this promise. In order to make effective use of code by complete strangers, code the logic of which you can not understand as well as they did, you must have (among other thing) a powerful, efficient debugging setup that's easy to get working.
Problem statement:
So! I don't know how to get a Nim debugging setup that's either powerful or efficient. I definitely can't do "easy to set up"! But I can at least identify two of the basics: step-by-step debugging and the automatic display of unmangled local and referenced non-local variables.
The frustrating thing is that, despite Nim's genuine complexities (compile to C or C++, then compile to assembly and link - and afterwards debug the Nim, not the base code) I can't help but feel that the community is tantalizingly close to a "good enough" solution.
People have shown at least some unmangled variables, have written python scripts for gdb or lldb (now - as for as I can suss out as a rube - lost or made obsolete) that at least attempt to get us closer to automatic and unmangled display.
Step-by-step debugging is likely to be a harder thing to fix <<deleted my guesses as to why - I'm not qualified to opine here>>. Whether hard or easy to fix, however, it's a real desire on the part of developers (especially those using third-party libraries); not offering it is a genuine toolchain weakness. Even reducing the number of weird jumps in the debugger though a debug-ready, non-optimized Nim codebase, unexpected to a developer not thoroughly conversant with Nim compiler internals, would help a lot.
In sum, I cannot yet feel that Nim, as a language, and even as a developer toolchain, is doomed to failure in this regard! Nim doesn't have to have debug tools as sophisticated as those available for C, C++, and Python because Nim leaves fewer handguns strewn about!
But the Nim ecosystem does, frankly, have a ways to go.
Not (mostly) in the language. Not even (often) in the case of individual tools. But there's not been enough tool-chain integration. Or, indeed, enough documentation written to detail, not the workings of a particular Nim-relevant tool, but the setup and correct usage of the Nim developer toolchain as an integrated whole. What the language offers in developer approachability and productivity, these issues in some measure take away.
Summing up:
If I worry about Nim as a production-ready programming language, I worry first about lack of debugging support. If I have one issue with the Nim toolchain, it is how manual and involved the integration between certain of the parts remains, showing up in cases like IDE setup and debugging. If I have one beef about the Nim documentation, it is lack of whole-system toolchain setup, usage, and troubleshooting guides. The capabilities of the tools don't matter except in so far as the Nim toolset delivers.
I posted above about a desire for line-by-line debugging, with the objective being to more quickly find exactly where errors occur, especially in code you didn't write yourself. This post provides a toy codebase, describes what 2025-model Nim, C, and GDB combine to offer (...as far as I can figure out how to get them to talk to one another through VS Code on Windows...), and compare it to my expectations. Dear reader, you may well have more interesting motivating examples; we'll toss a easy pitch at the Nim development environment for a warm-up.
My great hope is that you'll respond "Ah, if you set up your development environment like 1.2.3. ... then everything clicks and Nim+GDB steps through lines and shows relevant variables ungarbled!". My lesser hope is that you can offer ways to at least get others closer ... remembering that they (especially if not on Linux) may know far less about the specifics of how Nim, GNU, and other development tools talk to each other than do you.
type
Person = object
name: string
age: Natural # Ensures the age is positive
# Imagine that this code is deep in the bowels of a library, nested in a long call-stack.
proc ohSoHelpful(age: int): int =
let a = 1
let clue = "We don't yet realize that we want to see the whole clue."
echo age # Not asking for the right information.
echo clue[0] # Not asking for enough information.
# This is code we didn't write, or are rusty with. It bugs only in a specific use-case.
# Imagine it nestled in a long page of irrelevent code lines. Imagine it full of complex math.
# Our debugging method should track to it in one debug session, point our eyes right at it, and
# display every variable referenced within the context.
if age == 2 and clue[1] == 'e':
return age + a + 42
return age
# We're familar with this code. We - initially - trust ohSoHelpful.
proc addAYear(age: int): int =
return ohSoHelpful(age)
proc main() =
var people = [
Person(name: "Johan", age: 45),
Person(name: "Kleiner Johan", age: 2)
]
echo people[1].name
people[1].age = addAYear(people[1].age) # We expect Little John to be exactly one year older now.
echo "Kleiner Johan is now " & $people[1].age & "." # My, they grow up so fast...
main()
Compile with the flags
With these options, I expect that code will not be optimized; variables as I write them will be accessible in some debugger-accessible file, and that, therefore, they will show up in the local context and also be displayed without mangling.
Set up to debug with gdb. Not sure of what arguments, commands, and possibly scripts to pass to gdb, but in VS Code I currently have a launch.json configuration that includes
},
Put a breakpoint in main(), at the line "echo people[1].name". Start debugging. As expected, GDB stops at this line. Issue the <step over> command (F10 in VSCode) so we don't run into the echo proc. On the line "people[1].age = addAYear(people[1].age)", <step into> to enter the addAyear proc. We do so, as expected. Again, <step into>. We expect to go into ohSoHelpful(), but instead (on my machine) enter excpt.nim, nimFrame(). We'll be seeing a bunch of these redirects in our trip; Nim handles various complexities off-screen for the developer when they're typing or reading, but the details show up during debugging. Whenever this happens, issue the <step out of> command until you return to the code. Again, <step into>. After a bit, we enter ohSoHelpful(). <step over> until we reach the line "return age + a + 42" and stop. Here's the bug. To help me find it, I expect to see all local variables and other in-scope variables if locally referenced. On my machine, with a Feb-2025 Nim and toolchain setup, I don't see the local variable "clue". When I attempt to Watch it, I am shown "clue", but it contains no information. I am told by gdb that this has been optimized away. I do see the value of the variable "a" when I hover over it, but that's Intellisense, not the debugger. GDB does not show me "a" unless I Watch it. GDB does show me "age", but only in mangled form. Finally, the debugger shows me several mangled local variables and registers, but none of them are relevant for most debugging work, may be artifacts of the Nim-generated C code, and in any event only add distraction for the user in most cases.
Still at the line "return age + a + 42" issue the <step into> command, and slowly repeat this. In my case, I entered the destructor for the string "clue". Step out. We will (or soon will) go back to main(). However, we find ourselves, not at the line of code after the addAYear() call, but at the top of main(). <step into>. GDB takes us into the popFrame code. We're now debugging, not our or associated code, but the code to handle call-stack unwinding. In a typical debugging session, unless I explicitly ask for it, I do not expect so much granularity. <step over>. We unwind the call stack, call the destructor for the people struct, perhaps open (or fail to open) one or more CRT code files, and end the debugging session.
Some summary remarks: Nim, as a language, is approachable and understandable, striving to simplify on C without loss of control and efficiency where needed. Nim, as a debugging experience, is rather more complex than C (or C++). The complexity and even the low-level distraction and tedium come back with reinforcements!
And a closing Ask: I do not know if my debugging setup is at fault here. Getting the tools to work harmoniously together, manually, as a mostly-Windows developer ... is a P.I.T.A. If the above issues are not entirely on my end, then I propose, dear reader, the following requests for the debugging of non-optimized code:
And, arguably most important:
There is --stackTrace:off --debugger:native. But as I said, we learned to live without good debugging support. We have static typing and writeStackTrace() that you can insert in strategic places.
Why you describe here how "things should work" isn't clear to me. Believe it or not, I've used Delphi back when it offered an award winning debugging experience. How did they accomplish that? They didn't translate Delphi to C code first, ta-da! They had their own native codegen, their own IDE, their own debug info and their own debugger.
We only have a compiler that translates to C code where you cannot ever generate useful names due to C's preprocessor which defies all scoping rules.
Are debuggers even that useful? In a more or less complex system, each stack snapshot will contain a lot of variables, many holding complex structures. Staring at their values just overloads your with useless info. As far as my experience goes, figuring out what's wrong involves finding implicit assumptions that are manifested in the code, making a hypothesis about how these assumptions are broken, and then building a combination of variables that will serve as an indicator of whether the hypothesis is correct or not. Integrating the check into the code and rerunning is usually much easier than combining the values is the debugger.
On a side note, after I've started making assumptions explicit via assert and doAssert, my debugging life has become much simpler.
I mostly used debugger when working in embedded.
For other systems (aka with an actual OS) traces/ stacktrace / logs or unittest and printing stacktrace are often enough.