Specifically, I have two main requirements in mind.
- It should throw
BoundsError
with complete information.
- The
@inbounds
macro should work as intended for this type, i.e. elide the bounds checking entirely
Item 1 seems straightforward, just conditionally throw a BoundsError
with the right arguments. But, it seems that I am not sure about item 2; how does one check if @inbounds
is bypassing bounds checking in the first place? I tried using @code_warntype
and friends on @inbounds foo[i]
where foo isa Foo
, but it seems to give info about the operations of the inbounds macro itself.
A toy example.
struct Foo{T}
x::Vector{T}
y::Vector{Int}
end
import Base.getindex
function Base.getindex(f::Foo{T}, i::Int) where {T}
idx = f.y[i]
if idx > length(f.x)
throw(BoundsError(x, idx))
end
f.x[idx]
end
How can one transform this code to make @inbounds
work as intended, and how does one check if it is working as intended?
Base.@propagate_inbounds function Base.getindex(f::Foo{T}, i::Int) where {T}
idx = f.y[i]
Base.@boundscheck idx in eachindex(f.x) || throw(BoundsError(x, idx))
f.x[idx]
end
julia> function force_getindex(x, i)
@inbounds x[i]
end;
julia> x, y = [1, 2, 3], [1, 2, 3];
julia> resize!(x, 2);
julia> foo = Foo(x, y);
julia> foo[1]
1
julia> foo[2]
2
julia> foo[3]
ERROR: BoundsError: attempt to access 2-element Vector{Int64} at index [3]
Stacktrace:
[1] getindex(f::Foo{Int64}, i::Int64)
@ Main ./REPL[2]:3
[2] top-level scope
@ REPL[12]:1
julia> force_getindex(foo, 3)
3
Better to do
@boundscheck checkbounds(f.x, idx)
(Although if you do f.x[idx]
it does the bounds check anyway, so Iām not sure why the test is needed at all.)
1 Like
I guess particular example is too simple. The explicit bounds check logic that I am actually working on is bit more involved; even if the explicit check fails, the index would still point to a valid memory location i.e. would pass the bounds checking of the default types.
Should I update the question to include a more representative example?