Lets say I have this rust inspired Result type:
using Moshi.Data: @data
using Moshi.Match: @match
@data Result{OkT, ErrT} begin
Ok(OkT)
Err(ErrT)
end
#insert pretty printer here
Unlike Julia built in Union type, we can force the user to pattern match the output like in Rust.
If I want to return a result type, both Ok and Err need to have its types known in advance:
function return_result(x)
T_Ok = Int
T_Err = String
if x == 2
return Result.Ok{T_Ok, T_Err}(2)
else
return Result.Err{T_Ok, T_Err}("not two")
end
end
Then in my library I want to make a safe wrapper that verifies the input before running the user defined function with the input data:
function verify_input_before_function(f::Function, x)
if x != 2
return Result.Err("not two")
else
return Result.Ok(f(x))
end
end
# example usage
j = verify_input_before_function(2) do x
# if x is not two then julia will segfault
x+1
end
Unfortunately as this is not rust, the above function will not work as both Result.Err and Result.Ok need to have the two Ok and Err types defined first.
We know the Err type, but we donât know the Ok type, which is the return type of f(x).
After some searching around I found out about Base.return_types:
function verify_input_before_function(f::Function, x)
if x != 2
return Result.Err{Base.return_types(f, (typeof(x),))[1], String}("not two")
else
return Result.Ok{Base.return_types(f, (typeof(x),))[1], String}(f(x))
end
end
which works:
julia> j = return_result(2) do x
# if x is not two then julia will segfault
x+1
end
Result.Ok{Int64, String}(3)
julia> j = return_result(1) do x
# if x is not two then julia will segfault
x+1
end
Result.Err{Int64, String}("not two")
but unfortunately it makes my function type unstable:
julia> @code_warntype verify_input_before_function(2) do x
# if x is not two then julia will segfault
x+1
end
MethodInstance for verify_input_before_function(::var"#56#57", ::Int64)
from verify_input_before_function(f::Function, x) @ Main ~/scratchspace/io_uring_julia/tstable.jl:35
Arguments
#self#::Core.Const(Main.verify_input_before_function)
f::Core.Const(var"#56#57"())
x::Int64
Body::Main.Result.var"typeof(Result)"{_A, String} where _A
1 â %1 = Main.:!=::Core.Const(!=)
â %2 = (%1)(x, 2)::Bool
âââ goto #3 if not %2
2 â %4 = Main.Result::Core.Const(Main.Result)
â %5 = Base.getproperty(%4, :Err)::Core.Const(Main.Result.Err)
â %6 = Main.Base::Core.Const(Base)
â %7 = Base.getproperty(%6, :return_types)::Core.Const(Base.return_types)
â %8 = Main.typeof::Core.Const(typeof)
â %9 = (%8)(x)::Core.Const(Int64)
â %10 = Core.tuple(%9)::Core.Const((Int64,))
â %11 = (%7)(f, %10)::Vector{Any}
â %12 = Base.getindex(%11, 1)::Any
â %13 = Main.String::Core.Const(String)
â %14 = Core.apply_type(%5, %12, %13)::Type{Main.Result.Err{_A, String}} where _A
â %15 = (%14)("not two")::Main.Result.var"typeof(Result)"{_A, String} where _A
âââ return %15
3 â %17 = Main.Result::Core.Const(Main.Result)
â %18 = Base.getproperty(%17, :Ok)::Core.Const(Main.Result.Ok)
â %19 = Main.Base::Core.Const(Base)
â %20 = Base.getproperty(%19, :return_types)::Core.Const(Base.return_types)
â %21 = Main.typeof::Core.Const(typeof)
â %22 = (%21)(x)::Core.Const(Int64)
â %23 = Core.tuple(%22)::Core.Const((Int64,))
â %24 = (%20)(f, %23)::Vector{Any}
â %25 = Base.getindex(%24, 1)::Any
â %26 = Main.String::Core.Const(String)
â %27 = Core.apply_type(%18, %25, %26)::Type{Main.Result.Ok{_A, String}} where _A
â %28 = (f)(x)::Int64 # It knows the return type!!!!!!
â %29 = (%27)(%28)::Main.Result.var"typeof(Result)"{_A, String} where _A
âââ return %29
but the compiler obviously knows the return type of our passed in function, so whats stopping it from just setting those _A as Int64 instead of having this Any and all those type instabilities?