Speeding up the Rust edit-build-run cycle
davidlattimore.github.io
davidlattimore.github.io
This reminds me...
STOP putting your shitty dot-directories full of a mix of config, cache and data in my god damned home directory.
Concerned that not doing this will break things? Just check if you've dumped your crap in there already and if not then put it in the right places.
Worried about confusing existing users migrating to other machines? Document the change.
Still worried? How about this: I'll put a file called opt-into-xdg-base-directory-specification inside ${XDG_CONFIG_HOME:-$HOME/.config} and if you find that file you can rest assured I won't be confused.
Thanks in advance!
This!
Yes, it's bad. If you predate the specification then see options 2 (check), 3 (document) or 4 (come up with a new spec for opting in).
Thanks in advance.
Interesting. Is this true? In my work (java/kotlin, primarily in app code on a server, occasional postgres or frontend js/react stuff), I'm almost always reaching for a debugger as an enormously more powerful tool than println debugging. My tests are essentially the println, and if they fail for any interesting reason I'll want the debugger.
Debugging through log data still has a place, of course. However, trying to do all of your debugging through println is so much harder, even though it feels easier than learning to use a debugger.
Ultimately, if you have a good logging setup and kinda know where the issue is a quick log message could be faster than debugging if all you want to do is look a variable value.
I reach for a “real” debugger when necessary, but that’s less than 5% of the time.
E.g. I keep wondering whether the split between people who can't live without debuggers vs people who rarely use debuggers is actually people who use IDEs versus people who don't.
But I find that if I have to step around more than a handful of times to find the issue then I forget what happened five steps ago. So I teach for print debugging quite often.
I frequently find myself debugging large unfamiliar code bases, and typically it’s much easier to stick a breakpoint in and start following where it goes rather than blindly start instrumenting with print statements and hoping that you picked the right code path.
though you should note that I'm repeating their claims. What I think is hidden.
I've had to do that on a embedded system that didn't support debugging. It was hell.
Printf-debugging has its uses, but they are very niche (for instance when you don't have access to a properly integrated debugger). Logging on the other hand is useful, but logs are only one small piece of the puzzle in the overall debugging workflow - usually only for debugging problems that slipped into production and when your code runs on a server (as opposed to a user machine).
If, however, it’s something where I need to examine state at multiple times in the execution, I lose track in my mind of the state I’ve seen before. This is where print debugging shines: I can see how state evolved over time and spot trends.
Depends how fast your build/run cycle is and how many different prcocesses/threads whether a debugger will be faster/easier but a lot of it just comes down to preference. Most time spent debugging for me at least is spent thinking about the probable cause then choosing what state to look at.
You could do the same with a debugger setting up a breakpoint, but the logs can better surface key application-level decisions made in the process to get to the current bad state.
On a debugger I need to wind back on all functions, which it might get awful as some of them might be likely correct library calls that you need to skip over when going back in time, but that will take a huge portion of the functions called before the breakpoint. I don't think it's impossible to do with a debugger, but logging sort of bypasses the process of telling the debugger what's relevant so it can hide the rest, and it might already be in your codebase, but there's no equivalent annotations already there in the code to help the debugger understand what's important.
To me printf helps surfacing the relevant application-level process to get to a broken state, and debuggers help understand hairy situations where things have gone wrong at a lower level, say missing fields or memory corruption, but these days with safer languages lower level issues should be way less frequent.
---
On a side-note, it doesn't help debuggers that going back in time was really hard with variable-length instructions. I might be wrong here, but it took a while until `rr` came out.
I do think that complexities like that resulted in spending too much time dealing with hairy details instead of improving the UI for debugging.
I really value debuggers. I have spent probably half my career solving problems that weren’t possible to solve with a debugger. When I can fall back on it, it helps me personally quite a bit.
If I had the symbols, metadata, powerful debugger engine and a polished UI, I'll take that over printf everyday, but in the average situation printf is just too strong when fighting in mud.
Also, debuggers can do live evaluation of expressions, or do stuff like conditional breakpoints, or they can just simply add additional logs themselves. They are a very powerful utility.
Other than gdb, I can't name any Unix C source code debuggers. I believe they were working on Unix before GDB was created (wikipedia says gdb was created in 1986 - https://en.wikipedia.org/wiki/Gdb).
Plan 9 has acid, but from the man page and english manual, the debugger is closer to cli/tui than gui.
see https://9fans.github.io/plan9port/man/man1/acid.html and https://plan9.io/sys/doc/acid.html
I do care about usable tracebacks in error reports though.
Rust development is mostly fixing compile errors, anyway. Once it compiles, it often works the first time. What matters is compile time for error compiles, which is pretty good.
Incremental compile time for my metaverse client is 1 minute 8 seconds in release mode. That's OK. Takes longer to test a new version.
I am like you though, I don't typically resort to it immediately if I think I can figure out the problem with a quick log. And the times where I've not had access to a debugger with good UX, this tipping point can get pushed quite far out.
I guess 'many developers' here probably refers to web developers who don't use the debugger, cause it's mostly useless/perpetually broken in JS land ..? I rely heavily on the debugger; can't imagine how people work without one.
If I'm debugging some complicated TomEE application that might take 2 minutes to start up, then I'm absolutely reaching to an IntelliJ debugger as one of my first tools.
If I'm debugging some small command line application in Rust that will take 100ms to exhibit the failure mode, there's a very good chance that adding a println debug statement is what I'll try first
It can do any single expression, and the results are better than lldb, but it can’t do multiple statements and not everything in Rust can be one expression; you can’t use {} here
Last time I debugged Rust with CLion/RustRover, the debugger was the same as VSCode uses.
Rust support has improved in 2024 pretty strongly (before this year it just shelled out to lldb); the expr parser and more importantly the variable viewer are greatly improved since January.
...have you ever used the Visual Studio integrated debugger for C/C++ instead of 'raw' gdb/lldb without a UI frontend?
Outside that circle, maybe not.
Downloading symbols for first time from network symbol server is long but its not part of debugging cycle, at least after 1st run.
You lost me here.
Using a debugger to step through the code is a huge timesaver.
Inserting println statements, compiling, running, inserting more println, and repeating is very inefficient.
If you learn to use a debugger, set breakpoints, and step through code examining values while you go then the 8 seconds spent compiling isn’t an issue.
That all depends on how long it takes to compile and run.
> If you learn to use a debugger, set breakpoints, and step through code examining values while you go then the 8 seconds spent compiling isn’t an issue.
Meh, it's fine.
You still have to set new ones and re-run your code to trigger the reproduction iteratively if you don't know where anything is wrong.
Clicking "add logging break point here" and adding a print statement is really not very different. In my experience the hard part is know where you want to look. Stepping through all your code line by line and looking at all values every step is not a quick way to do anything. You have to divide and conquer your debugging smartly, like a binary search all over your call sites in the huge tree-walk that is your program.
Debuggers are pretty good when you want to understand someone else's code.
So like, sure: if I have some trivial one-off analysis tool I am building for which I am running into some issue I could figure out how to debug it, but even then I am going to have to figure out yet another debugging environment for yet another language, and also of course surmount the hassle of a ton of ensuring that sufficient debugging information is available and I'm running a build that is somehow not optimized enough for debugging and yet also not so slow that I'm gouging my eyes out, I could use a debugger, but I'd rather sit and stare at the code longer than start arguing with a debugger.
It's just another wannabe big scale dumbsterfire some big brain architect thought up, thinking he's implementing a platform that's actively being used by millions of users simultaneously, aka Google's or a few social media sites.
I think a lot of linux/mac folks tend to printf debug, while windows folks tend to use a debugger, and I suspect it is a culture based choice that is justified post hoc.
However, few things have been better for my code than stepping through anything complex at least once before I move on (I used to almost exclusively use printf debugging).
This is too slow and manual of a process to be a huge timesaver, you'd need a "time-travel" debugging capability that eliminate these to save time
2. Conditionally compiling some code like logging (not sure if matters for typical Rust projects, but for embedded C projects it's typical).
3. Conditionally compiling assertions to catch more bugs.
I'm using logs, because debugger breaks hardware. Very rarely do I need to reach debugger. Even when hard exception occurs, usually enough info is logged to find out the root cause of the bug.
What? Seems like you’re talking about embedded but I’ve done a lot of embedded projects in my time and I’ve never had a debugger that breaks the HW.
printf and friends are fantastic when applicable. Sometimes the cost to even do an async print or even building in any mode except stripped release is impossible though, which usually leads to !fun!.
In addition, debuggers tend to obscure the failure because they turn on all the hardware which tends to make bugs go away if they are related to power modes.
One of my "best" debuggers for embedded was putting an interactive interpreter over a serial interface on an interrupt so I could query the state of things when a device woke up even if it was hung--effectively a run time injectable "printf".
Crude, but it could trace down obscure bugs that were rare because the device would stay in the failure mode.
The bigeest problem was maintaining the database of code so that we knew exactly what build was on a device. We had to hash and track the universe.
And the OSS stuff never works correctly. I wind up debugging the OSS stuff more than my own hardware. And I've used a LOT of OSS (to the point that I wrote software to use a Beaglebone as my SWD debugger to work around all the idiocies--both commercial and OSS).
GitHub repo: https://github.com/davidlattimore/wild
List of his blog posts, a couple of which are about wild: https://davidlattimore.github.io/