I’ve been thinking similar things, though I think I would break it down into just three different default levels, and then allow tweaking exactly what those levels mean:
- debug, or what you call “max debug”; do as few optimizations as possible and retain as much debugging information as possible, for cases where you really want to have one-to-one correspondence between lines of Rust source and generated code, and have the maximum number of possible assertions.
- devel (similar to what you call “opt-debug”). This is the default mode that you get if you don’t specify anything. Optimizations that are reasonably quick to apply are turned on, but nothing that will affect compile speed too much, and things like parallel codegen can be on by default. The usual debug assertions are on. This is what you generally expect to do most development against, what you get if you just check out a project and do “cargo build”, etc.
- release, which defaults to no debug assertions, and fairly aggressive optimizations (possibly even including LTO). This actually defaults to no parallel codegen, since release builds are frequently done on dedicated build machines and reproducibility and final executable speed are generally considered more important than improving compile speed. This is what most package managers that build binary packages will use (I don’t know of any package managers that do incremental builds by default, they generally do a full clean build for the sake of reproducibility).
With these three default levels, and allowing you to tweak exactly what these levels mean or define your own other specialized profiles, I think there would be a reasonable set of defaults that would work for most projects, and you would avoid some of the “why is rust so slow” questions that are caused by doing default builds that have no optimization whatsoever (though to do real benchmarking you’d still want to compare release builds).
I think most projects could live with those three levels as specified, while some that had particularly demanding needs could increase the release optimization level more, or people who wanted faster or incremental release builds could enable parallel codegen at that level, or people who really need more profiles for a project could define their own.
While it’s a separate issue, I also think that all levels should include symbols by default. I really, really dislike shipping production code that doesn’t have symbols available to make sense of a core dump that happens in the field, but is hard to reproduce. Most software I ship is in the form of dpkg or rpm, and the build tools for both of those (Debian, Fedora) have convenient helpers for stripping symbols from the package and collecting them into a separate package-dbg that you can then use to get useful information out of a core dump. That feature has made tracking down bugs in the field so much easier.