Skip to content

Commit 583c5cc

Browse files
authored
Merge pull request #3924 from joshtriplett/cargo-hints-min-opt-level
Cargo: `hints.min-opt-level`
2 parents 8912d81 + 87c77bc commit 583c5cc

1 file changed

Lines changed: 234 additions & 0 deletions

File tree

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
- Feature Name: `cargo-hints-min-opt-level`
2+
- Start Date: 2026-02-22
3+
- RFC PR: [rust-lang/rfcs#3924](https://github.com/rust-lang/rfcs/pull/3924)
4+
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)
5+
6+
## Summary
7+
[summary]: #summary
8+
9+
Allow Rust library to provide a simple hint about the minimum opt-level to
10+
build them with.
11+
12+
## Motivation
13+
[motivation]: #motivation
14+
15+
When people build Rust projects, they have a choice of tradeoffs between the
16+
speed of the build and the performance of the code at runtime. Builds using the
17+
dev profile aim for minimal build time at the expense of unoptimized code at
18+
runtime; builds using the release profile spend more time building in order to
19+
improve runtime performance.
20+
21+
However, there are some library crates for which almost every use case wants
22+
optimization, because an unoptimized build provides so little performance as to
23+
be unusable. Such libraries typically include a note in their `README.md`
24+
warning users to turn on optimization even in the dev profile:
25+
26+
```toml
27+
[profile.dev.package."example-high-performance-package"]
28+
opt-level = 3
29+
```
30+
31+
This RFC adds a mechanism for crates to *hint* that builds should use
32+
optimization by default, while still allowing the top-level crate to easily
33+
override this. Handling this in dependencies makes it automatic for users, and
34+
also makes it easier to keep up with an evolving dependency tree.
35+
36+
## Guide-level explanation
37+
[guide-level-explanation]: #guide-level-explanation
38+
39+
Some libraries benefit strongly from optimization, or are hurt especially by
40+
builds without optimization, to the point that they expect almost no users to
41+
ever want to run them unoptimized. Such libraries can use the Cargo `hints`
42+
system to provide a minimum optimization level:
43+
44+
```toml
45+
[hints]
46+
min-opt-level = 2
47+
```
48+
49+
When building with a profile with a default optimization level lower than the
50+
`hints.min-opt-level` value, the crate will be built with the specified minimum
51+
optimization level instead. When building with a profile with a higher
52+
default optimization level, that higher optimization level will take precedence.
53+
54+
This applies whether the optimization level is the default for the profile, or
55+
is set by the user explicitly for the profile:
56+
57+
```toml
58+
[profile.dev]
59+
opt-level = 1
60+
# Does not override a higher hints.min-opt-level specified in a dependency.
61+
```
62+
63+
To override the hinted minimum optimization level, the top-level crate can use
64+
profile overrides to set the opt-level. Any opt-level specified via profile
65+
overrides will take precedence over any hint, whether the profile override
66+
applies to a specific crate or to all dependencies:
67+
68+
```toml
69+
[profile.dev.package."*"]
70+
opt-level = 1
71+
# Overrides hints.min-opt-level specified in a dependency.
72+
73+
[profile.dev.package."random-package"]
74+
opt-level = 0
75+
# Overrides hints.min-opt-level specified in a dependency.
76+
```
77+
78+
Note that setting `opt-level = "s"` or `opt-level = "z"` in the top-level
79+
crate's profile *will* override `min-opt-level` in a dependency, on the theory
80+
that crates optimizing for size are likely to want size-over-speed
81+
optimizations in their dependencies as well.
82+
83+
## Reference-level explanation
84+
[reference-level-explanation]: #reference-level-explanation
85+
86+
The `hints.min-opt-level` key requires an integer, and only supports numeric
87+
hint levels (0, 1, 2, 3). Non-numeric hint levels like `"s"` and `"z"` are not
88+
supported, because they don't fit into a strictly ordered progression, and
89+
because they're more likely to be use-case-dependent and better determined by
90+
the top-level crate.
91+
92+
`hints.min-opt-level`, like any hint, does not affect a crate's MSRV; older
93+
versions of Cargo will ignore it.
94+
95+
Any profile override will take precedence over `hint.min-opt-level`, including
96+
an override for all dependencies, an override for all build dependencies, or an
97+
override for a specific dependency.
98+
99+
A profile specifying an `opt-level` will not override a higher
100+
`hints.min-opt-level` specified in a dependency.
101+
102+
Note that a hint provided by a given library crate only applies to that
103+
specific crate, not that package's dependencies. If the code that needs
104+
optimizing is in a dependency, that dependency would need to add the hint.
105+
106+
There are two primary reasons to use this hint:
107+
- The typical full build (not just an incremental build) will be *sped up* by
108+
optimizing the crate.
109+
- The crate is disproportionately affected by optimization, such that it may be
110+
unusably slow (many *times* slower) without optimization.
111+
112+
## Drawbacks
113+
[drawbacks]: #drawbacks
114+
115+
Crates could overuse this mechanism, requiring optimization even when they
116+
don't actually need it. We should provide clear documentation recommending when
117+
to use it and when not to use it.
118+
119+
Even when used for the intended purpose, different users may have different
120+
tradeoffs. The override mechanism allows users to retain control, but the
121+
defaults will not be ideal for *all* users. The mechanism proposed by this RFC
122+
is making a deliberate tradeoff, and proposes that the defaults will work
123+
better for more users than the *current* default of not providing these hints,
124+
but this RFC does not claim the new default will be an improvement for every
125+
user.
126+
127+
If a crate using this mechanism wishes to nonetheless build with different
128+
optimizations within its own workspace, it would have to add an override.
129+
130+
### Limitations
131+
132+
Library crates cannot set this for dependencies they do not maintain; a crate
133+
can only set the min-opt-level for itself. This may cause issues for crates
134+
whose performance depends heavily on its dependencies; such crates may still
135+
have to rely on user documentation.
136+
137+
Library optimizations may not apply to code inlined or monomorphized by a
138+
user's crate.
139+
140+
## Rationale and alternatives
141+
[rationale-and-alternatives]: #rationale-and-alternatives
142+
143+
This mechanism intentionally does not offer a "maximum optimization level", nor
144+
does it support optimizing for size.
145+
146+
This mechanism intentionally does not provide access to any specific target
147+
feature flags, as this is typically something the top-level crate needs to
148+
retain full control over based on its minimum system requirements.
149+
150+
We could support multiple min-opt-level hints, one for dev-like profiles and
151+
one for release-like profiles, for crates that want a lower min-opt-level in
152+
the dev profile. However, `profile.release` already defaults to `opt-level =
153+
3`, so `min-opt-level` will generally never affect the release profile, only
154+
the dev profile. Thus, a single setting seems sufficient.
155+
156+
We could have a simple boolean, e.g. `optimize-in-dev = true`, and leave it to
157+
Cargo whether that means opt-level 1, 2, or 3. This would be simpler, but would
158+
prevent crates from determining whether they benefit from opt-level 3 (e.g.
159+
aggressive vectorization and loop unrolling) or not.
160+
161+
We could support `"z"` or `"s"` somehow. This would be more complex and require
162+
more design, since those aren't on a linear scale like the numeric optimization
163+
levels. Furthermore, these seem less likely to be usefully determined by
164+
dependencies: whether a dependency will have massive performance issues if
165+
built without optimization is something the dependency may know, while the need
166+
for size optimization seems likely to be use-case-dependent and better set by
167+
the top-level crate. (The use of crates *designed* for embedded, for instance,
168+
does not necessarily indicate that the user is size-constrained and would
169+
*prefer* size optimizations.)
170+
171+
We could have a hint apply recursively to dependencies. This seems like more
172+
control than a library crate could have, as a dependency may be used in
173+
multiple places in the crate graph.
174+
175+
## Prior art
176+
[prior-art]: #prior-art
177+
178+
Cargo already provides the profile overrides mechanism, for the top-level crate
179+
to specify the opt-level of individual crates.
180+
181+
This RFC builds on the `hints` mechanism, currently used for
182+
`hints.mostly-unused`. The
183+
[post announcing `hints.mostly-unused`](https://blog.rust-lang.org/inside-rust/2025/07/15/call-for-testing-hint-mostly-unused/)
184+
included
185+
[a section on future hints such as `min-opt-level`](https://blog.rust-lang.org/inside-rust/2025/07/15/call-for-testing-hint-mostly-unused/#future-hints).
186+
Cargo has
187+
[an issue discussing `min-opt-level`](https://github.com/rust-lang/cargo/issues/8501).
188+
189+
C and C++ compilers provide directives such as `#pragma optimize` or
190+
`__attribute__((optimize))`, which let individual files or functions define an
191+
optimization level.
192+
193+
## Unresolved questions
194+
[unresolved-questions]: #unresolved-questions
195+
196+
Should a profile with `opt-level = "s"` or `opt-level = "z"`
197+
override a dependency's `min-opt-level`? This RFC says it should,
198+
but sometimes users might not want that. We could also give more
199+
control over that.
200+
201+
## Future possibilities
202+
[future-possibilities]: #future-possibilities
203+
204+
`hints.min-opt-level` is a simple mechanism providing a single hint. There are
205+
many other possible optimization hints a library *might* wish to provide, and
206+
we could consider adding further hints for those in the future. Any such hint
207+
would need to balance the tradeoffs between value, additional complexity, and
208+
whether crates in the ecosystem know the right optimization level for their
209+
crate better than their users do.
210+
211+
We could in particular have a `max-opt-level`, for crates that don't benefit
212+
from `opt-level = 3` to lower the optimiation level to 2.
213+
214+
The mechanism to override a dependency's opt-level using `profile.dev.package`
215+
forces a given opt-level whether the dependency asks for higher or lower. Users
216+
of overrides, particularly those for `"*"`, might want a mechanism for setting
217+
a minimum without overriding a higher optimization level.
218+
219+
We may in the future want to change the default optimization level for the dev
220+
profile to 1, rather than 0. opt-level 1 includes optimizations that can make
221+
compilation *faster*, such as by sending less code to the codegen backend (e.g.
222+
LLVM). This might reduce the number of crates motivated to use this mechanism,
223+
but the mechanism would remain important, as there are library crates which
224+
strongly benefit from opt-level 2 or 3.
225+
226+
We may want to provide a further mechanism for libraries whose performance
227+
depends heavily on their dependencies to optimize those dependencies.
228+
229+
We may want to provide a mechanism for libraries to optimize code inlined or
230+
monomorphized by a user's crate. That would likely require compiler
231+
enhancements.
232+
233+
We may want to surface this in a visible way for users to see that it has been
234+
applied to their dependencies. For instance, we could note it in `--timings`.

0 commit comments

Comments
 (0)