Method for `Base.length` on a `const` type alias crashes julia

Julia is crashing when I try to define a Base.length method on a const type alias.

julia> import Base: length

julia> const A{N} = NTuple{N, Int64}
Tuple{Vararg{Int64, N}} where N

julia> length(a::A) = prod(a)

After this, julia does not throw any errors but crashes when I try to execute any other code. Is this something wrong with my code or is it a bug?
I managed to get the stderr output during the crash:

fatal: error thrown and no exception handler available.
BoundsError(a=(), i=1)
ijl_bounds_error_int at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/rtutils.c:187
ijl_get_nth_field at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/datatype.c:1569
ijl_get_nth_field_checked at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/datatype.c:1614
getindex at ./tuple.jl:31 [inlined]
iterate at ./tuple.jl:72 [inlined]
iterate at ./tuple.jl:72
unknown function (ip: 0x7c96086e7c75)
iterate at ./iterators.jl:298
isempty at ./essentials.jl:957
schedule at ./task.jl:849
unknown function (ip: 0x7c96086e9e60)
notify at ./condition.jl:154 [inlined]
#notify#646 at ./condition.jl:148 [inlined]
notify at ./condition.jl:148 [inlined]
notify at ./condition.jl:148 [inlined]
task_done_hook at ./task.jl:658
unknown function (ip: 0x7c96086e9e05)
_jl_invoke at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/gf.c:2895 [inlined]
ijl_apply_generic at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/gf.c:3077
jl_apply at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/julia.h:1982 [inlined]
jl_finish_task at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/task.c:320
start_task at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/task.c:1249
e[2 qschedule: Task not runnable
atexit hook threw an error: ErrorException("schedule: Task not runnable")
error at ./error.jl:35
#schedule#673 at ./task.jl:851
schedule at ./task.jl:849 [inlined]
uv_writecb_task at ./stream.jl:1166
unknown function (ip: 0x7c96086ec31c)
uv__write_callbacks at /workspace/srcdir/libuv/src/unix/stream.c:959
uv__stream_io at /workspace/srcdir/libuv/src/unix/stream.c:1294
uv__run_pending at /workspace/srcdir/libuv/src/unix/core.c:804
uv_run at /workspace/srcdir/libuv/src/unix/core.c:392
ijl_task_get_next at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/partr.c:478
poptask at ./task.jl:985
wait at ./task.jl:994
uv_write at ./stream.jl:1048
unsafe_write at ./stream.jl:1120
write at ./strings/io.jl:248 [inlined]
print at ./strings/io.jl:250
unknown function (ip: 0x7c96086ecce9)
_jl_invoke at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/gf.c:2895 [inlined]
ijl_apply_generic at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/gf.c:3077
showerror at ./errorshow.jl:151
unknown function (ip: 0x7c96086ed179)
_jl_invoke at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/gf.c:2895 [inlined]
ijl_apply_generic at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/gf.c:3077
_atexit at ./initdefs.jl:431
unknown function (ip: 0x7c96086ea827)
_jl_invoke at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/gf.c:2895 [inlined]
ijl_apply_generic at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/gf.c:3077
jl_apply at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/julia.h:1982 [inlined]
ijl_atexit_hook at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/init.c:280
ijl_exit at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/init.c:207
ijl_no_exc_handler at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/task.c:699
jl_finish_task at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/task.c:323
start_task at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/task.c:1249
error in running finalizer: BoundsError(a=(), i=1)
ijl_bounds_error_int at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/rtutils.c:187
ijl_get_nth_field at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/datatype.c:1569
ijl_get_nth_field_checked at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/datatype.c:1614
getindex at ./tuple.jl:31 [inlined]
iterate at ./tuple.jl:72 [inlined]
iterate at ./tuple.jl:72
unknown function (ip: 0x7c96086e7c75)
iterate at ./iterators.jl:298
isempty at ./essentials.jl:957
schedule at ./task.jl:849
notify at ./condition.jl:154 [inlined]
#notify#646 at ./condition.jl:148 [inlined]
notify at ./condition.jl:148 [inlined]
uvfinalize at ./asyncevent.jl:205
unknown function (ip: 0x7c96086ed7c5)
_jl_invoke at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/gf.c:2895 [inlined]
ijl_apply_generic at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/gf.c:3077
run_finalizer at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/gc.c:318
jl_gc_run_finalizers_in_list at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/gc.c:408
run_finalizers at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/gc.c:454
ijl_atexit_hook at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/init.c:299
ijl_exit at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/init.c:207
ijl_no_exc_handler at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/task.c:699
jl_finish_task at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/task.c:323
start_task at /cache/build/builder-amdci4-0/julialang/julia-release-1-dot-10/src/task.c:1249

Yet a similar definition of the method for intersect works as intended

julia> import Base: intersect

julia> const A{N} = NTuple{N, Int64}
Tuple{Vararg{Int64, N}} where N

julia> intersect(a::A{N}, as::A{N}...) where {N} = prod.([a, as...])
intersect (generic function with 43 methods)

julia> intersect((5,4,5,4), (4,5,3,3))
2-element Vector{Int64}:
 400
 180

version: julia version 1.10.4

You are redefining the length of a Tuple and this is causing your problems. Your length(a::A) is the same as trying to redefine length(a::NTuple{N, Int}). This is just one of the many possible consequences of type piracy.

If you want to do definitions like this, you’d need to define a wrapper struct over the Tuple instead of trying to use a type alias.

3 Likes

Thank you!
So if I understand correctly, the second method definition is also type piracy and I should avoid both of them, right?
The solution to this would be to define it as a new type?

struct A{N}
    a::NTuple{N, Int64}
end
4 Likes

Yeah, that’s exactly right.

4 Likes

The other alternative is to keep using the Tuple type but use a function other than Base.length. For example, MyModule.length or length_as_axes or whatever thing you’re actually trying to do here. The method you’re defining doesn’t match the usage of Base.length anyway, so there isn’t much benefit to it sharing the method table.

Although from your example, it appears you’re trying to encode something specific with this tuple so it probably makes the most sense to make a custom type. As a total shot in the dark, it seems you might be looking for something like CartesianIndices (helpful blog post):

julia> length(CartesianIndices((2,3,1))) # a 2x3x1 array of indices
6

julia> collect(CartesianIndices((2,3,1))) # what this looks like when iterated
2Ă—3Ă—1 Array{CartesianIndex{3}, 3}:
[:, :, 1] =
 CartesianIndex(1, 1, 1)  CartesianIndex(1, 2, 1)  CartesianIndex(1, 3, 1)
 CartesianIndex(2, 1, 1)  CartesianIndex(2, 2, 1)  CartesianIndex(2, 3, 1)
2 Likes

Thank you! This was actually a toy example and my use is different. This is my actual code:

struct Window{N}
    win::NTuple{N,ClosedInterval{Int}}
end
Base.length(w::Window{N}) where {N} = prod(d -> d.right - d.left + 1, w.win)

And length is then used for normalizing a function on the Window. Are there any reasons to make it a different function altogether? Perhaps some interface for which this does not quite match the intended original meaning (or even meaning it has for other types) of the function length?

That does sound very much like CartesianIndices! They similarly select rectangular windows of integer indexes. Instead of a ClosedInterval{Int}, they’re AbstractUnitRange{Int}s. But the functionality sounds quite similar.

Either way, you’d really only want to use Base.length if you’re describing the number of elements that could be generated by iteration.

2 Likes

This is a great tip! I just checked and both the methods that I showed on Window are defined on CartesianIndices.

julia> CartesianIndices((4:10, 6:10, 3:7)) |> length
175

julia> intersect(CartesianIndices((4:10, 6:10, 3:7)), CartesianIndices((6:10, 7:8, 5:15)))
CartesianIndices((6:10, 7:8, 5:7))

So to summarize my takeaway… I could use the CartesianIndices type and define additional functions on it which I need. The limitation is that if I would like to define a method of a function in another package, that would be an external function on an external type, thus type piracy. A third alternative is to define a type as a thin wrapper over the CartesianIndices type and not try to reinvent the wheel.

1 Like