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?