From 5f9b1bc6392f9e98f863d3155ca41473c9a4a921 Mon Sep 17 00:00:00 2001 From: Neven Sajko Date: Fri, 20 Feb 2026 10:00:46 +0100 Subject: [PATCH 1/2] throw `LightBoundsError` instead of `BoundsError` Context: While `BoundsError` is common in the Julia world, its use is problematic for compiler optimizations. More specifically, Julia is gaining the ability to eliminate heap allocations as of recently, even for code that is not eliminated by dead code elimination or eliminated by constant folding. After the compiler gains interprocedural escape analysis capabilities, elimination of heap allocations should become applicable more often. The problem with `BoundsError` is that a `BoundsError` value holds a reference to the array that was attempted to be indexed out-of-bounds. Thus a `BoundsError` throw escapes the value of the array. Thus a `BoundsError` throw makes the elimination of the underlying heap allocation impossible, unless the throw is first eliminated by `@inbounds` or by other compiler optimizations. The `LightBoundsError` exception from package [LightBoundsErrors.jl](https://github.com/JuliaArrays/LightBoundsErrors.jl) addresses this by storing `typeof(array)` and `axes(array)` instead of storing `array`. The new package is already being adopted as a dependency by [FixedSizeArrays.jl](https://github.com/JuliaArrays/FixedSizeArrays.jl) and by [MemoryViews.jl](https://github.com/BioJulia/MemoryViews.jl). Switching from `BoundsError` to `LightBoundsError` could hypothetically break a dependent package, in case the dependent branches on something like `exception isa BoundsError` while error handling in a `catch` block. --- Project.toml | 2 ++ src/StructArrays.jl | 2 ++ src/structarray.jl | 4 ++++ test/runtests.jl | 3 ++- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 20a95ed..c394201 100644 --- a/Project.toml +++ b/Project.toml @@ -5,6 +5,7 @@ version = "0.7.2" [deps] ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9" DataAPI = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" +LightBoundsErrors = "e21c612c-4641-4669-b9c3-3f4360ced9da" Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" [weakdeps] @@ -31,6 +32,7 @@ Documenter = "1" GPUArraysCore = "0.2" InfiniteArrays = "0.15" JLArrays = "0.2" +LightBoundsErrors = "1" LinearAlgebra = "1" KernelAbstractions = "0.9" OffsetArrays = "1" diff --git a/src/StructArrays.jl b/src/StructArrays.jl index bef1167..6262df4 100644 --- a/src/StructArrays.jl +++ b/src/StructArrays.jl @@ -17,6 +17,8 @@ include("lazy.jl") include("constructionbase.jl") include("tables.jl") +using LightBoundsErrors: checkbounds_lightboundserror + # Implement refarray and refvalue to deal with pooled arrays and weakrefstrings effectively import DataAPI: refarray, refvalue using DataAPI: defaultarray diff --git a/src/structarray.jl b/src/structarray.jl index 24ab0fb..b3d4c8c 100644 --- a/src/structarray.jl +++ b/src/structarray.jl @@ -342,6 +342,10 @@ map(c -> c[I...], Tuple(cols)) """ @inline @generated get_ith(cols::Tup, I...) = :(Base.Cartesian.@ntuple $(fieldcount(cols)) i -> @inbounds cols[i][I...]) +function Base.checkbounds(x::StructArray, I...) + checkbounds_lightboundserror(x, I...) +end + Base.@propagate_inbounds Base.getindex(x::StructArray, I...) = _getindex(x, to_indices(x, I)...) Base.@propagate_inbounds function _getindex(x::StructArray{T}, I::Vararg{Int}) where {T} diff --git a/test/runtests.jl b/test/runtests.jl index 4484283..99a61cc 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -11,6 +11,7 @@ using LinearAlgebra using Test using SparseArrays using InfiniteArrays +using LightBoundsErrors: LightBoundsError import Aqua import KernelAbstractions as KA @@ -43,7 +44,7 @@ Base.convert(::Type{Millimeters}, x::Meters) = Millimeters(x.x*1000) t = StructArray((a = a, b = b)) @test (@inferred t[2,2]) == (a = 4, b = 7) @test (@inferred t[2,1:2]) == StructArray((a = [3, 4], b = [6, 7])) - @test_throws BoundsError t[3,3] + @test_throws LightBoundsError t[3,3] @test (@inferred view(t, 2, 1:2)) == StructArray((a = view(a, 2, 1:2), b = view(b, 2, 1:2))) @test @inferred(parentindices(view(t, 2, 1:2))) == (2, 1:2) @test_throws ArgumentError parentindices(StructArray((view([1, 2], [1]), view([1, 2], [2])))) From 6b823832e1845e490014b2bf2ca0c2cfaa9f3802 Mon Sep 17 00:00:00 2001 From: Neven Sajko Date: Fri, 20 Feb 2026 10:11:03 +0100 Subject: [PATCH 2/2] v0.8 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index c394201..1ade710 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "StructArrays" uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a" -version = "0.7.2" +version = "0.8.0" [deps] ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9"