Missed optimization: Copied::position loses bounds-check elimination
Nobody has claimed this yet.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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