rust-lang / rust-lang/rust

Missed optimization: Copied::position loses bounds-check elimination

Open
#162,385 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

C-bug needs-triage
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

Code
#![crate_type = "lib"]

#[unsafe(no_mangle)]
pub fn bytes_position(s: &str, needle: u8) -> u8 {
    let Some(index) = s.bytes().position(|byte| byte == needle) else {
        return 0;
    };

    s.as_bytes()[index]
}

#[unsafe(no_mangle)]
pub fn copied_position(s: &str, needle: u8) -> u8 {
    let Some(index) = s
        .as_bytes()
        .iter()
        .copied()
        .position(|byte| byte == needle)
    else {
        return 0;
    };

    s.as_bytes()[index]
}

#[unsafe(no_mangle)]
pub fn slice_position(s: &str, needle: u8) -> u8 {
    let Some(index) = s
        .as_bytes()
        .iter()
        .position(|&byte| byte == needle)
    else {
        return 0;
    };

    s.as_bytes()[index]
}

Compile with:

rustc repro.rs --edition=2024 -O -C panic=abort --emit=asm
Expected behavior

None of these functions should need a bounds check.

Each iterator is newly constructed over all of s.as_bytes(). If position returns Some(index), an item was yielded at that position, which guarantees:

index < s.len()

The compiler already uses this fact and eliminates the bounds check in slice_position.

Actual behavior

Both bytes_position and copied_position retain a bounds check on the successful-search path:

cmpq    %rsi, %rax
jae     .Lpanic
movzbl  (%rdi,%rax), %eax
retq

.Lpanic:
callq   *core::panicking::panic_bounds_check@GOTPCREL(%rip)

By contrast, slice_position directly loads the byte:

movzbl  (%rdi,%rax), %eax
retq

The result is unchanged with:

-C opt-level=3 -C codegen-units=1 -C lto=fat

and with:

-C opt-level=3 -C target-cpu=native
Investigation

[str::Bytes](https://github.com/rust-lang/rust/blob/f248f4038796913873f11ca65b1b901e311c8dae/library/core/src/str/iter.rs) wraps a Copied<slice::Iter<'_, u8>>. Its position implementation therefore uses [Copied<I>](https://github.com/rust-lang/rust/blob/f248f4038796913873f11ca65b1b901e311c8dae/library/core/src/iter/adapters/copied.rs)::position.

Copied<I> does not override position, so it uses the generic Iterator::position implementation.

[slice::Iter::position](https://github.com/rust-lang/rust/blob/f248f4038796913873f11ca65b1b901e311c8dae/library/core/src/slice/iter/macros.rs) has a specialized implementation containing:

unsafe { assert_unchecked(i < n) };

The source comment specifically states that this assumption avoids a bounds check. Adding .copied() loses that specialized optimization.

One possible direction would be for Copied<I>::position to forward to the underlying iterator while copying the predicate argument:

#[inline]
fn position<P>(&mut self, mut predicate: P) -> Option<usize>
where
    P: FnMut(Self::Item) -> bool,
{
    self.it.position(|item| predicate(*item))
}

This would allow specialized implementations such as slice::Iter::position to remain effective.

Related: #119551 improved the generic Iterator::position implementation, but it did not cover this subsequent indexing/bounds-check-elimination difference.

Meta
rustc 1.100.0-nightly (f248f4038 2026-09-05)
binary: rustc
commit-hash: f248f4038796913873f11ca65b1b901e311c8dae
commit-date: 2026-09-05
host: x86_64-unknown-linux-gnu
release: 1.100.0-nightly
LLVM version: 23.1.1

This also reproduces with:

rustc 1.98.1 (48a229cea 2026-09-01)
LLVM version: 22.1.8

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the provided rustc repro and inspecting library/core/src/iter/adapters/copied.rs alongside the specialized slice::Iter::position implementation in library/core/src/slice/iter/macros.rs. Compare the generated assembly for copied_position and slice_position; done means the copied iterator path preserves the optimization and no longer emits a successful-path bounds check.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers, performance
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.