One option here is that cargo's release profile could default to a higher version than the target's baseline. That's admittedly easier said than done but it would solve the tension between "this is the absolute minimum we support" and "this is what 99% of people probably want".
Not that there aren't already differences, but things like that are quite a "surprise" to discover when CI is green and then deployment fails. While it is best to test what you deploy, it is, unfortunately, something that gets overlooked a lot of the time. An optimization issue is usually UB being exploited (in non-Rust languages) where code change is best anyways or a compiler bug with a straightforward workaround (stay on an older toolchain until it is fixed). Something like changing between build configurations this feels moreā¦fundamental somehow.
I'm assuming their suggestion would be to change the default alongside an edition, so it would be opt-in and wouldn't affect any existing crates.
I don't think this can be an edition thing. What crate's edition selection is used? The binary's crate? What if there are multiple binaries in the workspace and each has a different edition? Does the entire shared dependency tree get compiled twice to satisfy the edition semantics? Or does the minimum-used edition codegen semantics "win"?
Android asks which OS/API version to target when creating a new project. Maybe its something cargo could ask for on a new project or at least have the option exposed in the Cargo.toml. I think the biggest problem is most developers don't even know there are different versions. I personally think its okay to have rustc default to v1 and have cargo config default higher and make the option visible by default and documented. Let the developers decide.
Actually, how do we handle big.LITTLE targets? Only enable features available on the little cores? Pin the program to the big cores?
It would be the responsibility of CPU and operating system vendors to provide an API that distinguishes the common feature baseline across all cores and the per-core information. But they fail to do so, instead all cores just get clamped to the common baseline.
Intel even neutered AVX512 support on its alderlake P-cores via a firmware update because the E-cores didn't support it and that caused issues. Instead of working with OS vendors to provide the baseline/per-core distinction they doubled down and removed the actual logic units in later hardware revisions.
How realistic would it be to keep the toolchain itself compiled for x86-64-v1, but widely announce that starting on X version the default target-cpu would become x86-64-v2? Any individual (or distro) that still needed to worry about older CPUs could override the target-cpu to restore the old default, and everyone else would benefit from the newer ISA features.
For folks still dealing with x86-64-v1, I know that wouldnāt be the preferred option. But perhaps it would be tolerable enough that the relevant team(s) could be OK with the associated tradeoffs?
One of the major benefits of having a higher baseline is that the standard library itself could use more vector instructions and we ship a precompiled std. So it requires build-std or shipping multiple versions.
Shipping 2-3 extra copies of std doesn't seem especially onerous, e.g. for the x86-64 compilation levels, or new aarch64 major versions.
How difficult would it be to make every program check during startup, before control reaches main, whether it's being run on a processor that doesn't meet the ISA level it was compiled for, and if so, have it quit with a clear error message? I know I'd feel a lot better about changing the defaults if I could be sure that people who tried to run downloaded binaries on old systems  would get nice clear errors ā not just "Illegal instruction (core dumped)" ā and that the program was guaranteed to crash immediately on startup rather than after doing some unknown amount of work (possibly with permanent side effects and/or wasting a lot of someone's time).
not all programs go through Rust's main, so Rust may not have a chance to do that kind of a check. e.g. libSDL programs on Android, there the main is the Android system and that calls into libSDL which starts a separate thread to call your program's SDL_main (which the Rust language doesn't know is special in any way).
Also, on some OSes, Rust can't know if you're trying to write a GUI program (e.g. on Linux where a GUI program is really a normal program that just happens to connect to the Wayland or X server), so it would have to just send the error message to stderr or something, which is totally invisible in almost all GUI programs.
It doesn't have to be perfect.  A guard in the Rust runtime startup that writes the error message to stderr would be a complete solution for all command-line binary crates, and would at least ensure that GUI programs whose main is written in Rust, crash immediately on startup rather than possibly after the user has already done some work which then might get destroyed.
Some OSes let you do even better, for example I think I saw something go by about tagging ELF shared objects with subarchitecture requirements so that the dynamic loader will refuse to load modules that the CPU can't handle, which I think would also cover your libSDL scenario, and I know Windows does have a mechanism for reporting catastrophic startup errors graphically (albeit it might not be accessible to anyone outside of Microsoft). But these are refinements. The really important thing in my book is the guaranteed crash of some variety on startup.
We already have the ability to annotate certain functions so that additional CPU features are enabled. These become unsafe to call from functions not annotated with the same set of festures (or a superset).
Why couldn't we have it so a specific CPU feature is disabled for main? I.e. the default is v3, but we annotate main as baseline. Then main could do CPUID based checks and platform specific error messages before calling into the rest of the program.
Sure, you also need to disable those festures for _start and some other runtime parts, recursively for anything they need to call. Then you could call some simple logic to show a message box for your platform. For windows this would be relatively easy (a single ShowMessageBoxW call or something like that iirc). But Linux is the most complex case here, as there are many toolkits with many diffrent ways to show such a message box. Tricky but it should be solvable.
We don't have MIR anymore for most of these functions by the time we know what functions get called by main during the target feature check, so we can't codegen them again with less target features enabled.
Could you not let the developer provide a list of modules/functions and the target features to use? If they mix things up you get build errors indicating what needs to be added.
Basically I'm proposing a side channel for controlling target attributes, but having the compiler check your work. Tedious but should be possible?
Then the next step is to have tooling auto generate that side channel list.
The ergonomics does seem worse, but it still seems better than the status quo.
The path of least resistance for X and Wayland may well be to fork off a notify-send process.
Glibc's dynamic loader will check the ISA level if it is set in .note.gnu.property:
LLVM has the flag definitions, but AFAICS it only ever uses IBT and/or SHSTK for cf-protection.
This would still behave poorly for GUIs though.
Worst case, it seems like it should be possible to add an asm! block somewhere before main is called. Iād guess it would be at most a few dozen lines of assembly to do the feature detection and error handling. It might not be very pretty, but at least it would only have to be done once per platform.
Also wouldn't help for musl, and I always use static musl builds for prebuilt command line programs I distribute. It is much less of a headache even though you do have to replace the global allocator if you do multi-threading.
Doesn't help with terminal vs GUI. At least not on *nix.