# Why rustc 1.71 no longer eliminates Box allocations?

**URL:** <https://internals.rust-lang.org/t/why-rustc-1-71-no-longer-eliminates-box-allocations/22376>\
**Category:** compiler\
**Created:** [February 11, 2025, 5:19pm UTC](https://internals.rust-lang.org/t/why-rustc-1-71-no-longer-eliminates-box-allocations/22376 "2025-02-11T17:19:36Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![findepi](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/findepi/32/12550_2.png) [@findepi](https://internals.rust-lang.org/u/findepi)\
**Post date:** [February 11, 2025, 5:19pm UTC](https://internals.rust-lang.org/t/why-rustc-1-71-no-longer-eliminates-box-allocations/22376/1 "2025-02-11T17:19:36Z")

</div>

Following the example from [rust - How to make sure Box::new() really does heap allocation? - Stack Overflow](https://stackoverflow.com/a/75899303)

I compile the following code

```rust
#[no_mangle]
pub fn many_heap_calls() {
    for _ in 0..10 {
        let b = Box::new(42);
    }
}

```

- with rustc 1.70 the allocations are optimized away: [Compiler Explorer](https://rust.godbolt.org/z/fc8nEjdec)
- with rustc 1.71 the allocations are still there: [Compiler Explorer](https://rust.godbolt.org/z/Kcnh9dvrz)
- with rustc 1.84 the allocations are still there: [Compiler Explorer](https://rust.godbolt.org/z/xTvhf47re)

In the release notes [Announcing Rust 1.71.0 | Rust Blog](https://blog.rust-lang.org/2023/07/13/Rust-1.71.0.html) and [Release Rust 1.71.0 · rust-lang/rust · GitHub](https://github.com/rust-lang/rust/releases/tag/1.71.0) nothing stood out to me to cover this change.

**Questions:**

Why Rust 1.71 no longer eliminates allocation the way older versions did? Is this a feature? Can I somehow get the previous behavior?

**Context**

I.e. why this could matter.

I am aware that clippy can tell me about unused allocations and I can simply remove them from the code. The actual context is a more complicated. I am trying to use `Box<dyn Fn>` nested inside impl Fn (cause impl Fn cannot nest). The compiler can "see through" the Box dyn and realize the implementation is only one, inlining the code, which is awesome! I'm concerned the Box allocation remains:

> **[Compiler Explorer - Rust (rustc 1.84.0)](https://godbolt.org/z/871s5WPY4)**
>
> \#\[no\_mangle\]
> \#\[inline(never)\]
> pub fn foo(a: i32, b: i32, c: i32) -\> i32 {
> (a + b) \* c
> }
> 
> \#\[no\_mangle\]
> \#\[inline(never)\]
> pub fn bar(a: i32, b: i32, c: i32) -\> i32 {
> let apply\_a = doit(a);
> let apply\_b = apply\_a(b);
> let apply\_c =...

**More context**

This is related to my older question [Can compiler’s optimizer eliminate trivial heap allocation?](https://internals.rust-lang.org/t/can-compiler-s-optimizer-eliminate-trivial-heap-allocation/21541). Then it was shown to me that compiler can eliminate some allocations, although the example shared in [Can compiler’s optimizer eliminate trivial heap allocation? - #2 by the8472](https://internals.rust-lang.org/t/can-compiler-s-optimizer-eliminate-trivial-heap-allocation/21541/2) also exhibits "better" behavior under 1.70 and "worse" under 1.71

---

<div class="post-metadata">

**Author:** ![steffahn](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/steffahn/32/13288_2.png) [@steffahn](https://internals.rust-lang.org/u/steffahn)\
**Post date:** [February 11, 2025, 5:32pm UTC](https://internals.rust-lang.org/t/why-rustc-1-71-no-longer-eliminates-box-allocations/22376/2 "2025-02-11T17:32:35Z")

</div>

> [@findepi](#):
>
> Why Rust 1.71 no longer eliminates allocation the way older versions did?

One can probably [bisect](https://rust-lang.github.io/cargo-bisect-rustc/tutorial.html) for a better clue.

---

<div class="post-metadata">

**Author:** ![cuviper](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cuviper/32/1897_2.png) [@cuviper](https://internals.rust-lang.org/u/cuviper)\
**Post date:** [February 11, 2025, 5:54pm UTC](https://internals.rust-lang.org/t/why-rustc-1-71-no-longer-eliminates-box-allocations/22376/3 "2025-02-11T17:54:39Z")

</div>

AFAICS that's not actually _calling_ the allocator, just loading the relocated function pointer several times for the unrolled loop. That's still useless, but not as bad as an actual `malloc`+`free` every time.

(edit: it's not a function pointer -- see the `read_volatile` noted below)

---

<div class="post-metadata">

**Author:** ![findepi](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/findepi/32/12550_2.png) [@findepi](https://internals.rust-lang.org/u/findepi)\
**Post date:** [February 11, 2025, 5:58pm UTC](https://internals.rust-lang.org/t/why-rustc-1-71-no-longer-eliminates-box-allocations/22376/4 "2025-02-11T17:58:53Z")

</div>

My bad. Thanks for correcting me, @cuviper! Should I still assume it's not without a performance impact, if called many times? (Context here is functions for a database engine, so hopefully the code will be called billions of times.)

---

<div class="post-metadata">

**Author:** ![findepi](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/findepi/32/12550_2.png) [@findepi](https://internals.rust-lang.org/u/findepi)\
**Post date:** [February 11, 2025, 6:02pm UTC](https://internals.rust-lang.org/t/why-rustc-1-71-no-longer-eliminates-box-allocations/22376/5 "2025-02-11T18:02:43Z")

</div>

Thanks @steffahn , this tool is very cool!

I tried to use that with

```rust
$ cat script.sh
#!/usr/bin/env bash

set -x
rm lib.s
set -eu

rustc -C opt-level=3 --emit asm --crate-type=lib src/lib.rs
# must be found
cat lib.s | sed '1,/^_many_heap_calls/d' | grep .
# must not have allocations
! cat lib.s | sed '1,/^_many_heap_calls/d' | grep ___rust_no_alloc_shim_is_unstable

```

then

```rust
cargo bisect-rustc --start 1.70.0 --end 1.71.0 --script=./script.sh

```

the tail of output is

```rust
1 versions remaining to test after this (roughly 1 steps)
installing nightly-2023-05-26
rust-std-nightly-aarch64-apple-darwin: 26.80 MB / 26.80 MB [======================================================================================================================================================] 100.00 % 3.34 MB/s testing...
RESULT: nightly-2023-05-26, ===> Script returned error
uninstalling nightly-2023-05-26

searched toolchains nightly-2023-04-15 through nightly-2023-05-27

********************************************************************************
Regression in nightly-2023-05-26
********************************************************************************

fetching https://static.rust-lang.org/dist/2023-05-25/channel-rust-nightly-git-commit-hash.txt
nightly manifest 2023-05-25: 40 B / 40 B [======================================================================================================================================================================] 100.00 % 508.96 KB/s converted 2023-05-25 to c373194cb6d882dc455a588bcc29c92a96b50252
fetching https://static.rust-lang.org/dist/2023-05-26/channel-rust-nightly-git-commit-hash.txt
nightly manifest 2023-05-26: 40 B / 40 B [======================================================================================================================================================================] 100.00 % 683.81 KB/s converted 2023-05-26 to a2b1646c597329d0a25efa3889b66650f65de1de
looking for regression commit between 2023-05-25 and 2023-05-26
fetching (via remote github) commits from max(c373194cb6d882dc455a588bcc29c92a96b50252, 2023-05-23) to a2b1646c597329d0a25efa3889b66650f65de1de
ending github query because we found starting sha: c373194cb6d882dc455a588bcc29c92a96b50252
get_commits_between returning commits, len: 9
  commit[0] 2023-05-24: Auto merge of #111260 - petrochenkov:effvisperf7, r=cjgillot
  commit[1] 2023-05-24: Auto merge of #111919 - matthiaskrgr:rollup-8qcdp0q, r=matthiaskrgr
  commit[2] 2023-05-25: Auto merge of #111925 - Manishearth:rollup-z6z6l2v, r=Manishearth
  commit[3] 2023-05-25: Auto merge of #111575 - alex:patch-1, r=pietroalbini
  commit[4] 2023-05-25: Auto merge of #111933 - matthiaskrgr:rollup-m10k3ts, r=matthiaskrgr
  commit[5] 2023-05-25: Auto merge of #111473 - compiler-errors:opaques, r=lcnr
  commit[6] 2023-05-25: Auto merge of #110906 - ekusiadadus:ekusiadadus/fix-bash-complete-etc, r=albertlarsan68
  commit[7] 2023-05-25: Auto merge of #111512 - petrochenkov:microdoc2, r=GuillaumeGomez
  commit[8] 2023-05-25: Auto merge of #86844 - bjorn3:global_alloc_improvements, r=pnkfelix
ERROR: no CI builds available between c373194cb6d882dc455a588bcc29c92a96b50252 and a2b1646c597329d0a25efa3889b66650f65de1de within last 167 days

```

of these, the ` commit[8] 2023-05-25: Auto merge of #86844 - bjorn3:global_alloc_improvements, r=pnkfelix` line stand out to me. Maybe because [https://github.com/rust-lang/rust/pull/86844](https://github.com/rust-lang/rust/pull/86844) is something i already opened based on `git log 1.70.0..1.71.0 --pretty=oneline | grep -i alloc` in the rust repo.

I will read the PR, but i still don't know: is this a feature, or a bug? how can i get previous behavior?

---

<div class="post-metadata">

**Author:** ![cuviper](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cuviper/32/1897_2.png) [@cuviper](https://internals.rust-lang.org/u/cuviper)\
**Post date:** [February 11, 2025, 6:26pm UTC](https://internals.rust-lang.org/t/why-rustc-1-71-no-longer-eliminates-box-allocations/22376/6 "2025-02-11T18:26:27Z")

</div>

Looks like this `read_volatile` (added in 86844) can't be optimized away:

> <https://github.com/rust-lang/rust/blob/8c61cd4df8434573190336b8f16169f3c2b22a7a/library/alloc/src/alloc.rs#L92-L94>

cc @bjorn3

---

<div class="post-metadata">

**Author:** ![pitaj](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/pitaj/32/11262_2.png) [@pitaj](https://internals.rust-lang.org/u/pitaj)\
**Post date:** [February 11, 2025, 6:29pm UTC](https://internals.rust-lang.org/t/why-rustc-1-71-no-longer-eliminates-box-allocations/22376/7 "2025-02-11T18:29:57Z")

</div>

[Relevant zulip thread](https://rust-lang.zulipchat.com/#narrow/stream/197181-t-libs.2Fwg-allocators/topic/When.20will.20__rust_no_alloc_shim_is_unstable.20be.20removed.3F)

Might be worth taking a look at some alternatives

---

<div class="post-metadata">

**Author:** ![findepi](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/findepi/32/12550_2.png) [@findepi](https://internals.rust-lang.org/u/findepi)\
**Post date:** [February 11, 2025, 7:41pm UTC](https://internals.rust-lang.org/t/why-rustc-1-71-no-longer-eliminates-box-allocations/22376/8 "2025-02-11T19:41:02Z")

</div>

My local benchmarks showed 6x perf difference (for functions from [Compiler Explorer](https://godbolt.org/z/871s5WPY4) shared above)

I found the tracking issue [Missed optimization/perf oddity with allocations · Issue #128854 · rust-lang/rust · GitHub](https://github.com/rust-lang/rust/issues/128854). It looked like there was no "business justification" so posted there.

Thank you All very much for your help and useful pointers!

---

<div class="post-metadata">

**Author:** ![bjorn3](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/bjorn3/32/2736_2.png) [@bjorn3](https://internals.rust-lang.org/u/bjorn3)\
**Post date:** [February 11, 2025, 9:01pm UTC](https://internals.rust-lang.org/t/why-rustc-1-71-no-longer-eliminates-box-allocations/22376/9 "2025-02-11T21:01:45Z")

</div>

> [@cuviper](#):
>
> Looks like this `read_volatile` (added in 86844) can't be optimized away:

Not much we can do about that until we either bless directly linking rlibs or get rid of the allocator shim all together. I have a WIP PR for the latter by using weak symbols, but I still need to get it working on Windows.

---

<div class="post-metadata">

**Author:** ![findepi](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/findepi/32/12550_2.png) [@findepi](https://internals.rust-lang.org/u/findepi)\
**Post date:** [February 12, 2025, 11:10am UTC](https://internals.rust-lang.org/t/why-rustc-1-71-no-longer-eliminates-box-allocations/22376/10 "2025-02-12T11:10:53Z")

</div>

As perhaps not uncommon, it turned out my real problem is somewhere else. I saw assembly difference, so i run benchmarks and the benchmark said that one variant is slower. But the real real difference comes not from assembly code difference. It comes from inlining capabilities of the functions being benchmarked.

Different "inlineability" may be a result of allocations (later eliminated), or closures (also later eliminated) or something else. I captured my result in [Allocation elimination, loops and benchmark results · GitHub](https://gist.github.com/findepi/89497d13a3a249a1d2d1b6d7c2f8b927) if anyone is interested.

Thank you All very much again for your replies!

---

<div class="post-metadata">

**Author:** ![findepi](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/findepi/32/12550_2.png) [@findepi](https://internals.rust-lang.org/u/findepi)\
**Post date:** [February 13, 2025, 11:49am UTC](https://internals.rust-lang.org/t/why-rustc-1-71-no-longer-eliminates-box-allocations/22376/11 "2025-02-13T11:49:39Z")

</div>

A question about bisect. How do you use `--prompt`? It shows the compiler output, so I know how to use it for compiler messages. How to use it e.g. with `cargo bench`?

(I am observing a [regression in nightly](https://internals.rust-lang.org/t/optimization-opportunity-for-box-dyn-fn-which-is-done-for-dyn-fn-and-regression-in-nightly/22381) and would love to be able to find a corresponding commit.)

---

<div class="post-metadata">

**Author:** ![steffahn](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/steffahn/32/13288_2.png) [@steffahn](https://internals.rust-lang.org/u/steffahn)\
**Post date:** [February 13, 2025, 4:01pm UTC](https://internals.rust-lang.org/t/why-rustc-1-71-no-longer-eliminates-box-allocations/22376/12 "2025-02-13T16:01:41Z")

</div>

Using cargo bench should just work, shouldn't it? E.g.

```bash
cargo bisect-rustc --start … --end … --prompt -- bench

```

The final `--` separates a custom cargo command, i.e. bisection calls `cargo` with anything that follows this `--` as extra arguments; for the example above that’s a simple `cargo bench` then.

(If a `--script` is defined instead, that script will receive those arguments instead.)

With `--prompt`, I also like `--preserve` to more quickly re-trace the steps (if I made a mistake or just want to do it again for double-checking) to skip the downloads. You can later remove the installed toolchains normally via rustup. If they are too many for manual work, it could work programmatically e.g. with something like

```bash
rustup toolchain list | grep '^bisector-nightly-' | xargs -n1 rustup toolchain uninstall

```

With benchmarks, you don't really care about compiler-behavior either, I guess, so `--preserve-target` is a decent option to add, too; then you can rerun the bench (when re-doing bisection, or even with the `> retry` option from `--prompt`) without recompiling the benchmarking suite.

* * *

For fully manual interactive investigation at each stage… well one thing I've done before is opening a second terminal and just working with the installed `bisector-nightly-…` toolchain there.

`cargo bisect-rustc` doesn’t seem to be designed for this use-case; nonetheless, I was able to get a somewhat convenient fully-interactive experience with something like

```bash
… --prompt --script sh -- -c 'bash <&3' 3<&0

```

the `--prompt` involves forwarding stdout&stderr already anyway, the file descriptor 3 is used to route stdin into the inner `bash`, too, which doesn’t inherit the normal stdin. Then just do whatever you like, using simple `cargo` commands and the like within that shell (this apparently works by environment variables being set to [define the toolchain](https://github.com/rust-lang/cargo-bisect-rustc/blob/1662d45a2221632849b497ab34d75788a1ec26ce/src/toolchains.rs#L254) that `rustup` should provide, and [the target directory](https://github.com/rust-lang/cargo-bisect-rustc/blob/1662d45a2221632849b497ab34d75788a1ec26ce/src/toolchains.rs#L290)), then leave it with `exit` or Ctrl+D to fall back into the question from interactive `--prompt`.

The above even works without extra script files, because the part behind `--` gets passed to `sh`, so the short filehandle-redirecting can simply be defined inline with the command. [The `“3<&0”` part is not passed to `cargo bisect-rustc` at all; it makes `3` point to stdin before `bisect-rustc` even starts, and could appear _anywhere_ in the whole command.]

If you want the right answer to be preselected using a command within the shell, `exit 0` and `exit 1` can work… setting up aliases is also possible ~ apparently one would need to abuse `--rcfile` for this, but it works then:

```bash
… --prompt --script bash 3<&0 -- -c "bash <&3 --rcfile <(cat ~/.bashrc; echo \"alias b='exit 0'; alias r='exit 1'\")"

```

for instance this sets up `b` for `baseline` and `r` for `regressed`, which is pre-selected in the `--prompt` interaction (based the exit code, following the default `regress=error` logic).

---

<div class="post-metadata">

**Author:** ![findepi](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/findepi/32/12550_2.png) [@findepi](https://internals.rust-lang.org/u/findepi)\
**Post date:** [February 13, 2025, 6:52pm UTC](https://internals.rust-lang.org/t/why-rustc-1-71-no-longer-eliminates-box-allocations/22376/13 "2025-02-13T18:52:38Z")

</div>

This is awesome writeup, thank you @steffahn !

The `--prompt --script sh -- -c 'bash <&3' 3<&0` is the fully interactive mode i was hoping for, will give it a try!

---

<div class="post-metadata">

**Author:** ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)\
**Post date:** [August 7, 2026, 6:53pm UTC](https://internals.rust-lang.org/t/why-rustc-1-71-no-longer-eliminates-box-allocations/22376/14 "2026-08-07T18:53:05Z")

</div>

This topic was automatically closed 540 days after the last reply. New replies are no longer allowed.
