Skip to content

Bounds check may not be properly eliminated with (Bound<usize>, Bound<usize>) indexer but eliminated with some cases #157909

Description

@anatawa12

Indexing slice with (Bound<usize>, Bound<usize>) may not get optimized property even with inline attribute.
Full compiler explorer: https://godbolt.org/z/96eoPsaTj

Simplest case

pub mod bounds_indexer {
    use std::collections::Bound;

    pub fn test(buf: &[u8]) -> Option<&[u8]> {
        if buf.len() < 4 {
            None
        } else {
            Some(&buf[(Bound::Included(4), Bound::Unbounded)])
        }
    }
}
Original case I initially working on, with impl RangeBounds
pub mod bounds_indexer_with_range_bounds {
    // possible with stable
    use std::ops::RangeBounds;

    pub fn test(buf: &[u8]) -> Option<&[u8]> {
        if buf.len() < 4 { None } else { Some(index(buf, 4..)) }
    }

    //#[inline(always)] // regardless. It looks rustc will inline this function without attribute
    fn index(buf: &[u8], range: impl RangeBounds<usize>) -> &[u8] {
        &buf[(range.start_bound().map(|x| *x), range.end_bound().map(|x| *x))]
    }
}
Other complex cases with several ways to construct (Bound, Bound)
#![cfg_attr(feature="nightly", feature(range_into_bounds))]

#[cfg(feature="nightly")]
pub mod bounds_indexer_with_into_range_bounds {
    // nigltly only requires #![feature(range_into_bounds)]
    use std::ops::IntoBounds;

    pub fn test(buf: &[u8]) -> Option<&[u8]> {
        if buf.len() < 4 { None } else { Some(index(buf, 4..)) }
    }

    //#[inline(always)] // regardless. It looks rustc will inline this function without attribute
    fn index(buf: &[u8], range: impl IntoBounds<usize>) -> &[u8] {
        &buf[range.into_bounds()]
    }
}

// Following is optimized and does not introduce bounds check if none of above two are not compiled.
// If either or both of above cases are compiled, `<(Bound<usize>, Bound<usize>) as SliceIndex<[u8]>>::index`
// will be called and bounds check will be ran in `SliceIndex::index`
pub mod bounds_indexer {
    use std::collections::Bound;

    pub fn test(buf: &[u8]) -> Option<&[u8]> {
        if buf.len() < 4 {
            None
        } else {
            Some(&buf[(Bound::Included(4), Bound::Unbounded)])
        }
    }
}

Following two cases get optimized out.

pub mod bounds_indexer_with_range_bounds_manually_mapped {
    use std::collections::Bound;
    use std::ops::RangeBounds;

    pub fn test(buf: &[u8]) -> Option<&[u8]> {
        if buf.len() < 4 { None } else { Some(index(buf, 4..)) }
    }

    fn index(buf: &[u8], range: impl RangeBounds<usize>) -> &[u8] {
        &buf[match range.start_bound() {
            Bound::Included(&i) => i,
            Bound::Excluded(i) => i.checked_add(1).expect("overflow"),
            Bound::Unbounded => 0,
        }..match range.end_bound() {
            Bound::Included(&i) => i,
            Bound::Excluded(i) => i.checked_sub(1).expect("overflow"),
            Bound::Unbounded => buf.len(),
        }]
    }
}
much shorter case
pub mod raw_index {
    pub fn test(buf: &[u8]) -> Option<&[u8]> {
        if buf.len() < 4 { None } else { Some(&buf[4..]) }
    }
}

Surprisingly, Any of bad case will be optimized if only one of bad cases are compiled.
I think rustc can optimize indexing if there is only one <(Bound<usize>, Bound<usize>) as SliceIndex<[u8]>>::index invocation and can be inlined without almost no code size cost.

What's expected

I expected to see no bounds checks are emitted for all versions

Meta

rustc --version --verbose:
Basically testing with playground and compiler explorer

rustc 1.96.0 (ac68faa20 2026-05-25)
binary: rustc
commit-hash: ac68faa20c58cbccd01ee7208bf3b6e93a7d7f96
commit-date: 2026-05-25
host: x86_64-unknown-linux-gnu
release: 1.96.0
LLVM version: 22.1.2
Compiler returned: 0

Edit hisotry

  • I found the Surprising part is not specific to the implementation, but for any implementation with <(Bound<usize>, Bound<usize>) as SliceIndex<[u8]>>::index so modified accordingly with new fact

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.A-codegenArea: Code generationC-optimizationCategory: An issue highlighting optimization opportunities or PRs implementing suchI-slowIssue: Problems and improvements with respect to performance of generated code.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions