Search code examples
juliabounds-checker

@propagate_inbounds in Julia


Consider the following case:

struct A <: AbstractArray{Int, 2}
    N::Int
end

struct B <: AbstractArray{Int, 2}
    A::A
    Func
end
...
@inline Base.getindex(A::A, i::Int, j::Int) = begin
    @boundscheck (1 <= i <= A.N && 1 <= j <= A.N) || throw(BoundsError())
    i - j
end

Base.@propagate_inbounds Base.getindex(B::B, i::Int, j::Int) = begin
    B.Func(B.A[i, j])
end

Looking at the docs and some examples I was sure that the bounds checks would be eliminated while executing "@inbounds b[i, j]" (b is an array of type B). However, it is not the case. What am I missing?


Solution

  • This works for me using just your definitions:

    julia> f1(x, i, j) = return x[i,j]
           f2(x, i, j) = @inbounds return x[i,j]
    
    julia> f1(A(5), 123, 123)
    ERROR: BoundsError
    Stacktrace:
     [1] getindex at ./REPL[3]:2 [inlined]
     [2] f1(::A, ::Int64, ::Int64) at ./REPL[4]:1
    
    julia> f2(A(5), 123, 123)
    0
    
    julia> f1(B(A(5), identity), 123, 123)
    ERROR: BoundsError
    Stacktrace:
     [1] getindex at ./REPL[3]:2 [inlined]
     [2] getindex at ./REPL[3]:6 [inlined]
     [3] f1(::B, ::Int64, ::Int64) at ./REPL[4]:1
    
    julia> f2(B(A(5), identity), 123, 123)
    0
    

    As I mentioned in my comment, @inbounds only works when it's in a type-stable function. Testing it at global scope (like at the REPL) or in a function that's not type stable (like one that references a non-constant global or an abstractly-typed field or array) will not remove those bounds checks.

    Note that your definition of B will likely lead to type instabilities as Julia cannot know what function it is storing. They're not evident in the minimal example you posted, but this might be the root cause in your more complicated original problem. A better definition would be:

    struct B{F} <: AbstractArray{Int, 2}
        A::A
        Func::F
    end