I have a (mutable, edit) struct with a field that has type Union{T, Nothing}
, and I've been trying to implement Base.isless
for this struct, but I cannot figure out how to make JET.jl happy...
A simple function works fine,
julia> function foo(a::Union{Int, Nothing}, b::Union{Int, Nothing})
if a isa Nothing
if b isa Nothing
return false
else
return true
end
else
if b isa Nothing
return false
else
return a < b
end
end
end
foo (generic function with 1 method)
julia> JET.@report_opt foo(1, 2)
No errors detected
julia> JET.@report_call foo(1,2)
No errors detected
But once the a
and b
are encapsulated in a struct, JET complains:
julia> mutable struct Bar
x::Union{Int, Nothing}
end
julia> function Base.isless(a::Bar, b::Bar)
if a.x isa Nothing
if b.x isa Nothing
return false
else
return true
end
else
if b.x isa Nothing
return false
else
return a.x < b.x
end
end
end
julia> JET.@report_opt Base.isless(Bar(1), Bar(2))
═════ 2 possible errors found ═════
┌ isless(a::Bar, b::Bar) @ Main ./REPL[16]:12
│┌ <(x::Int64, y::Nothing) @ Base ./operators.jl:343
││ runtime dispatch detected: isless(x::Int64, y::Nothing)
│└────────────────────
│┌ <(x::Nothing, y::Int64) @ Base ./operators.jl:343
││ runtime dispatch detected: isless(x::Nothing, y::Int64)
│└────────────────────
julia> JET.@report_call Base.isless(Bar(1), Bar(2))
═════ 3 possible errors found ═════
┌ isless(a::Bar, b::Bar) @ Main ./REPL[16]:12
│┌ <(x::Nothing, y::Nothing) @ Base ./operators.jl:343
││ no matching method found `isless(::Nothing, ::Nothing)`: isless(x::Nothing, y::Nothing)
│└────────────────────
│┌ <(x::Int64, y::Nothing) @ Base ./operators.jl:343
││ no matching method found `isless(::Int64, ::Nothing)`: isless(x::Int64, y::Nothing)
│└────────────────────
│┌ <(x::Nothing, y::Int64) @ Base ./operators.jl:343
││ no matching method found `isless(::Nothing, ::Int64)`: isless(x::Nothing, y::Int64)
│└────────────────────
Using JET v0.8.9 on Julia v1.9.2.
Any ideas?
... I guess JET.@report_opt
is already telling me, quite clearly, that it ends up in dynamic dispatch..., so of course making the Bar field type a type parameter <: Union{Int, Nothing}
works...
julia> mutable struct Bar{T<:Union{Int, Nothing}}
x::T
end
julia> function Base.isless(a::Bar, b::Bar)
if a.x isa Nothing
if b.x isa Nothing
return false
else
return true
end
else
if b.x isa Nothing
return false
else
return a.x < b.x
end
end
end
julia> using JET
julia> JET.@report_opt Base.isless(Bar(1), Bar(2))
No errors detected
julia> JET.@report_call Base.isless(Bar(1), Bar(2))
No errors detected
... but that's not what I was looking for - I would like to be able to modify the field - make it a Nothing and vice versa...
The issue is that currently, the Julia compiler doesn't realise that if you load the same field multiple times from the same struct, the field is of the same type. So, even though you checked that a.x is not nothing and b.x is not nothing, when you do the comparison, Julia has "forgotten" that you checked
You solve it by storing them in a variable
See more here: https://aviatesk.github.io/JET.jl/stable/jetanalysis/#no-matching-method-found-(x/y-union-split)
Right! I should’ve re-read the documentation for JET… Thanks!
Jesper Stemann Andersen has marked this topic as resolved.
worth noting that JET is correct in this specific case -- Bar
is mutable after all, so some other thread could write a nothing
to a.x
or b.x
after your check. Only accessing the field once and storing that in a local variable technically changes the behaviour of your program
Right ... I was also wondering whether that point should be added as a footnote to the JET documentation...
Last updated: Dec 28 2024 at 04:38 UTC