Default Compilation Mode in Cargo

Hello,

I have a small suggestion regarding the default behavior of "Cargo" during the compilation process.

Normally, the standard behavior of a compiler is to compile code into a target output without adding any extra instructions. However, in this case, Cargo, via rustc, compiles in "debug" mode by default, which adds extra debugging instructions that should normally be explicitly requested.

So, to get the standard behavior, we always have to add --release.

My proposal is as follows:

  • Make "Release" the default compilation mode.
  • For debug mode, it would require explicitly adding --debug.
  • For backward compatibility, we could, for example, define the default behavior in the Cargo.toml file with something like:
[default]
profile = "debug"

I know we can tinker with something using [alias], but it's still just a workaround.

Another motivation for making release mode the default is when I come across videos or articles with performance benchmarks from people unfamiliar with Rust, comparing its performance using just a cargo build without the --release flag, which frustrates me every time. :bulb: Not to mention other optimizations they are unaware of, such as:

[profile.release]
strip = true
panic = "abort"
lto = true
opt-level = 3
debug = false
codegen-units = 1

Thank you for taking the time to read my post. :pray:

--release takes substantially longer to build than the current default, so this would slow down people's development cycle: it would mean people have to wait longer after making a change before they can see if that change compiles, runs, passes tests.

6 Likes

This has had some past discussion at Public view of rust-lang | Zulip team chat

I don't think switching to release by default is the best solution, but there's definitely a long-standing problem of communicating debug vs release behaviors to users.

1 Like

I think the default build should always include debug information (strip = false, debug = true) but I think there's a strong case for raising its default opt-level a little, like maybe opt-level = 1.

(GCC nowadays has an -Og mode that's exactly what we want here: "Optimize debugging experience. -Og should be the optimization level of choice for the standard edit-compile-debug cycle, offering a reasonable level of optimization while maintaining fast compilation and a good debugging experience." If LLVM had that I would already have filed the request to make cargo use it in the debug profile. It doesn't, but opt-level=1 is probably not that different.)

1 Like

I think it might make sense to have an opt-level = "fast" or similar, whose primary purpose is to apply optimizations that tend to speed up compilation rather than slowing it down.

Regarding strip and debug: I think it'd be reasonable to evaluate whether the default build should have full debug information (suitable for single-stepping in a debugger), or whether it should just include enough to give acceptable backtraces. Doing the latter allows building much faster.

4 Likes

I did not know that!

I almost never find it useful to single-step in a debugger, but I do want backtraces to be more detailed than just function names (I want to see the values of each function's arguments) and I don't know whether that's an available intermediate level of debug info.

What I really want to avoid, though, is defaulting to no debug information. That'd be giving up too much in the name of build speed, IMO.

I'm pretty sure opt-level=0 actually does include such optimizations, though i'm not sure where i read that.

Normally, the standard behavior of a compiler is to compile code into a target output without adding any extra instructions. However, in this case, Cargo, via rustc, compiles in "debug" mode by default, which adds extra debugging instructions that should normally be explicitly requested.

It's worth pointing out that debug info is not created using executable instructions, it's typically metadata placed either in a separate section or a separate file entirely which is not loaded during normal program execution.

2 Likes

Numbers at Feedback request: Performance improvements from reducing debug info

Well, mostly — OP may also be talking about cfg(debug_assertions) and ub_checks, which do directly add more behavior into the built program in debug mode.

It's also possible that they're imagining something like MSVC's debug mode STL which does significantly more than the release mode STL.

But I expect the most likely is that they just misunderstand the difference of applying optimizations or not in an AOT compiled language.

This is common speculation that it's people who are not familiar with the concept of optimizing compilers.

Finished `release` profile [optimized + debuginfo] target(s) in 9.94s

links to Profiles - The Cargo Book

Those docs could be extended to spend a few more words on what those profiles are intended to result in. E.g. "fast compilation, detects bugs, debugger works, program will be slow at runtime".

Then again, that's assuming that people click the link.

Assuming they realize it's a link and that their terminal supports links [1]. I wouldn't be surprised if the only people who realize that there's a link are those who are already well aware of Cargo profiles. It's very much not good UX at all.

I feel that the long-term solution is to have a very clear, verbose diagnostic message that also explains how to turn itself off, preferably with something like git config so you don't have to figure out config.toml. Natural language, none of this silly machine speak:

dev profile [unoptimized + debuginfo] target(s)

(Btw, this isn't the 80s anymore, we can afford properly pluralized nouns!)


  1. I myself have never noticed it until today -- I don't think the macOS Terminal.app supports links? IDEA's builtin terminal doesn't. On Windows cmd.exe obviously doesn't; Windows Terminal does but I literally only now noticed that WT renders the text with a dotted underline and that that means it's a clickable link! ↩ī¸Ž

I think the trade-off between verbosity for novice users vs terseness for advanced users could be solved by having Cargo installation be a bit stateful, and show more verbose messages in "new" installations.
For example, cargo run could print a warning that it's going to be slow, until user uses cargo run --release for the first time (or for a day, or few times — exact logic can be tweaked). To avoid unnecessary verbosity in always-fresh installs in CI environments, Cargo could also suppress the teaching messages when CI env var is set.

3 Likes

I don't quite get why that would be though. C and C++ has the same thing, and I never heard about there being confusion over there. Debug mode in Rust is often even slower at runtime than in C++, sure, but not by that much.

And what about other compiled languages, like Haskell? My knowledge of Haskell is fairly limited (never got past the basics) but I'm pretty sure it is compiled and uses LLVM, so it likely has the same issue.

So why is this only a problem for Rust? Are there no users coming from hosted languages to compiled languages other than Rust?

At least on Linux using split debug info (unpacked variety) tends to help with build and link times. For some reason this is not the default. It probably should be.

2 Likes

Haskell has an LLVM backend, but by default it compiles to Cmm: 5.10. GHC Backends — Glasgow Haskell Compiler 9.12.1 User's Guide Unlike rustc, GHC does almost all optimizations that matter itself. In fact it has to as LLVM is not able to perform most optimizations GHC does AFAIK. That said, GHC probably also compiles pretty slow code for -O0.

In C the difference between unoptimized and optimized builds is maybe 2-3x, but in Rust it is closer to 20-30x.

1 Like