# Type stable Base.return\_types - is it possible?

**URL:** <https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992>\
**Category:** General Usage\
**Created:** [August 31, 2025, 11:47am UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992 "2025-08-31T11:47:08Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![minetest2048](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/minetest2048/32/45961_2.png) [@minetest2048](https://discourse.julialang.org/u/minetest2048)\
**Post date:** [August 31, 2025, 11:47am UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/1 "2025-08-31T11:47:08Z")

</div>

Lets say I have this rust inspired `Result` type:

```julia-auto
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:

```julia-auto
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:

```julia-auto
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`:

```julia-auto
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-auto
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-auto
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?

---

<div class="post-metadata">

**Author:** ![abraemer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/abraemer/32/51403_2.png) [@abraemer](https://discourse.julialang.org/u/abraemer)\
**Post date:** [August 31, 2025, 11:58am UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/2 "2025-08-31T11:58:17Z")

</div>

> [@minetest2048](#):
>
> ```julia-auto
> 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
> 
> ```

I believe this is a case of [Be aware when Julia does not specialize](https://docs.julialang.org/en/v1/manual/performance-tips/#Be-aware-of-when-Julia-avoids-specializing)

Try:

```julia-auto
function verify_input_before_function(f::F, x) where F
    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

```

---

<div class="post-metadata">

**Author:** ![minetest2048](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/minetest2048/32/45961_2.png) [@minetest2048](https://discourse.julialang.org/u/minetest2048)\
**Post date:** [August 31, 2025, 12:28pm UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/3 "2025-08-31T12:28:27Z")

</div>

Doesn’t help because `Base.return_types(f, (typeof(x),))[1]` is not type stable:

```julia-auto
function verify_input_before_function(f::F, x) where F
    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

julia> @code_warntype verify_input_before_function(2) do x
           # if x is not two then julia will segfault
           if x !=2 error("segfault!") end 
           x+1
       end  
MethodInstance for verify_input_before_function(::var"#32#33", ::Int64)
  from verify_input_before_function(f::F, x) where F @ Main ~/scratchspace/io_uring_julia/tstable.jl:35
Static Parameters
  F = var"#32#33"
Arguments
  #self#::Core.Const(Main.verify_input_before_function)
  f::Core.Const(var"#32#33"())
  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
│ %29 = (%27)(%28)::Main.Result.var"typeof(Result)"{_A, String} where _A
└── return %29

```

What is type stable is to use `typeof(f(x))` instead of using Base.return\_types, but it will run the function even when it is unsafe to do those:

```julia-auto
julia> function verify_input_before_function2(f::F, x) where F
           if x != 2 
               return Result.Err{typeof(f(x)), String}("not two")
           else
               return Result.Ok{typeof(f(x)), String}(f(x))
           end
       end
verify_input_before_function2 (generic function with 1 method)

julia> @code_warntype verify_input_before_function2(3) do x
           # if x is not two then julia will segfault
           if x !=2 error("segfault!") end 
           x+1
       end  
MethodInstance for verify_input_before_function2(::var"#44#45", ::Int64)
  from verify_input_before_function2(f::F, x) where F @ Main ~/scratchspace/io_uring_julia/tstable.jl:44
Static Parameters
  F = var"#44#45"
Arguments
  #self#::Core.Const(Main.verify_input_before_function2)
  f::Core.Const(var"#44#45"())
  x::Int64
Body::Main.Result.var"typeof(Result)"{Int64, String}
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.typeof::Core.Const(typeof)
│ %7 = (f)(x)::Int64
│ %8 = (%6)(%7)::Core.Const(Int64)
│ %9 = Main.String::Core.Const(String)
│ %10 = Core.apply_type(%5, %8, %9)::Core.Const(Main.Result.Err{Int64, String})
│ %11 = (%10)("not two")::Core.Const(Main.Result.var"typeof(Result)"{Int64, String}(Main.Result.var"##Storage#Err"{Int64, String}("not two")))
└── return %11
3 ─ %13 = Main.Result::Core.Const(Main.Result)
│ %14 = Base.getproperty(%13, :Ok)::Core.Const(Main.Result.Ok)
│ %15 = Main.typeof::Core.Const(typeof)
│ %16 = (f)(x)::Int64
│ %17 = (%15)(%16)::Core.Const(Int64)
│ %18 = Main.String::Core.Const(String)
│ %19 = Core.apply_type(%14, %17, %18)::Core.Const(Main.Result.Ok{Int64, String})
│ %20 = (f)(x)::Int64
│ %21 = (%19)(%20)::Core.PartialStruct(Main.Result.var"typeof(Result)"{Int64, String}, Any[Main.Result.var"##Storage#Ok"{Int64, String}])
└── return %21

julia> verify_input_before_function2(3) do x
           # if x is not two then julia will segfault
           if x !=2 error("segfault!") end 
           x+1
       end  
ERROR: segfault!
Stacktrace:
 [1] error(s::String)
   @ Base ./error.jl:44
 [2] #41
   @ ~/scratchspace/io_uring_julia/tstable.jl:53 [inlined]
 [3] verify_input_before_function2(f::var"#41#42", x::Int64)
   @ Main ~/scratchspace/io_uring_julia/tstable.jl:46
 [4] top-level scope
   @ ~/scratchspace/io_uring_julia/tstable.jl:51

# it should've returned Result.Err{Int64, String}("not two") instead of "crashing"

```

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [August 31, 2025, 12:38pm UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/4 "2025-08-31T12:38:59Z")

</div>

> [@minetest2048](#):
>
> After some searching around I found out about `Base.return_types`

Don’t use that, except interactively. It’s not public API, for one. Even if it were, this is really not how you’d want to use it.

BTW, your post is an example of a “XY problem”:

- [https://xyproblem.info](https://xyproblem.info)

- [XY problem - Wikipedia](https://en.wikipedia.org/wiki/XY_problem)

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [August 31, 2025, 1:32pm UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/5 "2025-08-31T13:32:41Z")

</div>

This code is type-stable and only uses public API:

```julia-auto
function verify_input_before_function(f::Function, x)
	fT = Base.promote_op(f, typeof(x))
    if x != 2 
        return Result.Err{fT, String}("not two")
    else
        return Result.Ok{fT, String}(f(x))
    end
end

```

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [August 31, 2025, 1:47pm UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/6 "2025-08-31T13:47:06Z")

</div>

`promote_op` is not public either.

```julia-repl
help?> Base.promote_op
  │ Warning
  │
  │ The following bindings may be internal; they may change or be removed in future versions:
  │
  │ • Base.promote_op

```

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [August 31, 2025, 2:29pm UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/7 "2025-08-31T14:29:44Z")

</div>

It’s public in Julia LTS (1.10):

> Julia `Base` and standard library functionality described in the [the documentation](https://docs.julialang.org/) that is not marked as unstable (e.g. experimental and internal) is covered by [SemVer](https://semver.org/).

So the fact that `promote_op` is not marked `public` in 1.11 should be a bug (unless it’s a breaking change, but those Julia claims to avoid).

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [September 1, 2025, 12:03am UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/8 "2025-09-01T00:03:18Z")

</div>

> [@minetest2048](#):
>
> the compiler obviously knows the return type of our passed in function

No it doesn’t. What you’re doing would only work for _concrete_ inferred types. If the compiler inferred an abstract type for a return value, like `something(::Some{Any})`, then it can’t possibly match the actual return type at runtime. Compiler-inferred types are reliable enough for performance, but they _can_ change, so if you want to specify concrete types _semantically_, you need to do it manually. Obviously, that’s the typical practice in statically typed languages where even generic functions typically rely entirely on parameters in the header, so that’s probably what you should aim for.

C++ does have `decltype` to infer a type from an input expression, but `decltype(auto)` for return types seems avoided in practice and by other languages because it does make call chains harder for a human to follow. Another critical difference is that statically typed languages like C++ disallow multiple possible concrete return types (well, there’s covariant return types for pointers/references to subclasses), whereas dynamically typed languages like Julia allow it as an implicitly boxed feature, hence the possibility of inferring abstract types for return values. If you’re confident that _in practice_, `f(x)` call’s concrete return type is inferrable, you were basically looking for a riskier Julia approximation of `decltype(f(x))`. As you’ve found, `Base.return_types` doesn’t do it because it’s designed to infer multiple methods for various combinations of input types, not one particular call signature. `typeof(f(x))` doesn’t cut it because it executes the input expression and gets the runtime type. It’s possible in principle, but I don’t know of a direct way to do it, it’s all repurposing internals with different purposes.

> [@aplavin](#):
>
> described in the [the documentation](https://docs.julialang.org/) that is not marked as unstable

I’m pretty sure the default “internal; they may change or be removed in future versions” warning counts as being marked as unstable.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [September 1, 2025, 2:47am UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/9 "2025-09-01T02:47:25Z")

</div>

> [@Benny](#):
>
> > [@aplavin](#):
> >
> > described in the [the documentation](https://docs.julialang.org/) that is not marked as unstable
> 
> I’m pretty sure the default “internal; they may change or be removed in future versions” warning counts as being marked as unstable.

There was no such warning before v1.11, which is @aplavin’s point. And it seems valid. I looked into the PR that introduced `public`, which also modified the part in the docs from which @aplavin quoted, and it seems the idea was to mark all symbols mentioned in the docs as `public` before v1.11 was to be released. I didn’t know about this fact, or forgot about it. PR:

> <https://github.com/JuliaLang/julia/pull/50105>
>
> NOTE: This PR is not a complete solution to the "public interfaces are typically… not well specified in Julia" problem. We would need to implement much than this to get to that point. Work on that problem is ongoing in Base and packages and contributions are welcome.
> 
> \- \[x\] https://github.com/JuliaLang/JuliaSyntax.jl/pull/320 is a key part of this PR (implements the syntax change) and must be merged first.
> 
> ~This PR is extended by https://github.com/JuliaLang/Pkg.jl/pull/3511 which defines the semantic interpretation of scoped export and should be merged or closed simultaneously.~
> 
> This adds a mechanism for "scoped export", which marks a symbol as part of the public API without loading it into the global namespace on \`using\`.
> 
> Goals
> 
> \- "API markers" that show up in docstrings
> \- The ability to query if something is an "API marked" thing
> \- The ability to discover "API marked" things
> \- Unambiguous definition of public API accessible from the REPL
> \- Little to no effort required by package authors to adopt this slightly more formal API specification
> 
> \_first three goals originally posted by @dalum in https://github.com/JuliaLang/julia/issues/49973#issuecomment-1579073814\_
> 
> 
> Changes this PR makes to achieve those goals
> 
> \- Add \`public, list, of, names\`
> \- \`names(Module)\` reports all public names (including exports)
> \- \`Base.isinternal(::Module, ::Symbol)\` is an internal method which determines if a symbol is internal.
> \- \`?my\_symbol\` flags the docstrings of unexported symbols as internal.
> \- The public API of Base becomes "behavior described in docstrings of public symbols (exported symbols are automatically public)"
> 
> Adoption
> 
> \- Internal unexported methods require no action. Internal exported methods shouldn't exist. Public exported methods require no action. Public unexported methods need to be "scoped export"ed to adopt this feature.
> \- Until folks scoped export their public but unexported symbols, they will be flagged as internal in \`help?\>\` mode.
> \- Compat.jl should be able to provide \`@public\` so folks can adopt this without dropping support for earlier versions of Julia.
> 
> TODO
> 
> \- \[x\] The current scoped export syntax is horrible \`export (scoped-true), list, of, names\`. This should be bikeshed and then implemented using JuliaSyntax.jl because @c42f says it's landing soon and I'd rather work with that than the scheme parser. \[current syntax: \`public list, of, names\`\]
> \- \[x\] Check that this doesn't lead to bad parsing error messages \[none that I could find\]
> \- \[x\] Need to mark as public all symbols in Base that are mentioned in the documentation
> \- ~Need to mark as public all symbols in Stdlib that are mentioned in the documentation~ \[this can be deferred to another PR before 1.11 lands because the docstring note is milder now\].
> \- \[x\] The warning in internal docstrings is ugly—it was the first place I could put a warning, not how I think it should look. Again: bikeshed first, then implement the specifics.
> \- \[x\] ~Only tab-complete to public methods. (@mcabbott's suggestions)~ moved to #50717
> \- \[x\] Update documentation to reflect \`public\` rather than \`export scoped=true\`.
> \- ~Move the changes in https://github.com/JuliaLang/Pkg.jl/pull/3511 to here.~
> 
> This implements #38162. See also:
> \- #42117
> \- #30204
> \- #48819
> \- #49973
> \- JuliaLang/Pkg.jl#3512

That said, it is my impression that the docs included and mentioned lots of functionality which was definitely _not_ meant to be public at all, so clearly the “described in the the documentation” quote was also not completely true.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [September 2, 2025, 6:05am UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/10 "2025-09-02T06:05:55Z")

</div>

I misread aplavin’s comment for sure, thanks.

> [@nsajko](#):
>
> That said, it is my impression that the docs included and mentioned lots of functionality which was definitely _not_ meant to be public at all, so clearly the “described in the the documentation” quote was also not completely true.

My impression was that phrase referred to the docstrings in the `Base` chapter, not function calls in code examples. `promote_ops` is only used in this [element type computation example](https://docs.julialang.org/en/v1/manual/methods/#Output-type-computation), which is the same in 1.10 and 1.11. `return_types` is also used there and [the typed globals section](https://docs.julialang.org/en/v1/manual/variables-and-scoping/#man-typed-globals). Still, not great that something that’s “fragile” and “should be avoided” is THE documented way to compute output types, whether for 1 operation or array broadcasting. Its docstring actually states verbatim “The guess is in part based on type inference, so can change any time,” which makes it surprising that it’s documented to be a good alternative to the “brittle type inference” of `return_types`.

> **Very similar examples to those of this thread are warned against in the extended docstring of \`promote\_op\`, and the 'fix' is to use an additional internal function that's never mentioned in the documentation on top of recomputing the output type if it turns out to be wrong at runtime. That obviously conflicts with the request for a type parameter at compile time.**
>
> ```julia-auto
> function invalid_usecase1(f, xs)
> R = promote_op(f, eltype(xs))
> ys = similar(xs, R)
> for i in eachindex(xs, ys)
> ys[i] = f(xs[i])
> end
> return ys
> end
> 
> function invalid_usecase2(f, xs)
> R = promote_op(f, eltype(xs))
> if isconcretetype(R)
> ys = similar(xs, R)
> else
> ys = similar(xs, Any)
> end
> for i in eachindex(xs, ys)
> ys[i] = f(xs[i])
> end
> return ys
> end
> 
> ```

---

<div class="post-metadata">

**Author:** ![ForceBru](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/forcebru/32/21389_2.png) [@ForceBru](https://discourse.julialang.org/u/ForceBru)\
**Post date:** [September 2, 2025, 2:46pm UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/11 "2025-09-02T14:46:38Z")

</div>

> [@Benny](#):
>
> ```julia-auto
> R = promote_op(f, eltype(xs))
> ys = similar(xs, R)
> 
> ```

Since all of these are “invalid usecases”, is there a proper way of extracting the return type of a _future_ function call?

I feel like this is not usually done in Julia for some reason. But how should I preallocate storage for returned values then? I can’t know the return type before calling the function, so I guess it’s not possible to do this?

Why doesn’t Julia have a “proper” `Function` type that specifies both the return type and arguments’ types?

```julia
function verify_input(f::Function{R, T}, x::T) where {R, T}
   if x != 2
      Result.Err{R, String}("error")
   else
      y::R = f(x) # that y::R is known at compile-time since x::T
      Result.Ok{R, String}(y)
   end
end

```

Here, `Function{R, T1, T2, ..., Tk}` is the type of a function with the following signature: `func(::T1, ::T2, ..., ::Tk)::R`.

1. The compiler knows that `x::T`, so the range of `R` is restricted. `R` is decided using the usual way of choosing the most specific method to call given the types of arguments. If multiple methods with different return types match, `R<:Union{R1, R2, ...}`. BTW, when _calling_ `verify_input`, `T` is a concrete type, right? So there can be only one corresponding method `f(::T)::R`, so there should be one concrete `R` type.
2. Thus, the possible return types are known and we’re happy.

---

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [September 2, 2025, 3:01pm UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/12 "2025-09-02T15:01:49Z")

</div>

> [@ForceBru](#):
>
> BTW, when _calling_ `verify_input`, `T` is a concrete type, right? So there can be only one corresponding method `f(::T)::R`, so there should be one concrete `R` type.

That would be nice, but not in julia’s current incarnation:

```julia-auto
f(x) = x < 0.5 ? 0 : pi

```

With the call

```julia-auto
f(rand())

```

the input type is `Float64`, and all the compiler can say is that the output is `Union{Int, Irrational{:π}}`.

Or, take something like:

```julia-auto
f(n::Int) = Array{Float64, n}(undef, ntuple(_ -> 3, Val(n)))

```

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [September 2, 2025, 3:51pm UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/13 "2025-09-02T15:51:58Z")

</div>

> [@ForceBru](#):
>
> Why doesn’t Julia have a “proper” `Function` type that specifies both the return type and arguments’ types?

That really depends on what exactly do you mean/want. For example, Julia already provides type assertion and function composition, which could be interpreted to be exactly what you want. For example:

```julia-repl
julia> comp = Base.Fix2(typeassert, Float64) ∘ sin
Base.Fix2{typeof(typeassert), Type{Float64}}(typeassert, Float64) ∘ sin

julia> typeof(comp)
ComposedFunction{Base.Fix2{typeof(typeassert), Type{Float64}}, typeof(sin)}

julia> comp isa ComposedFunction{Base.Fix2{typeof(typeassert), Type{Float64}}}
true

```

So, instead of your imagined `Function{R}`, you can use `ComposedFunction{Base.Fix2{typeof(typeassert), Type{R}}}`. You’d perhaps want a type alias to make it shorter.

On the other hand, it might be that you instead want more something like this stalled PR:

- [RFC: Introduce `TypedCallable` by topolarity · Pull Request #55111 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/pull/55111)

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [September 2, 2025, 8:08pm UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/14 "2025-09-02T20:08:29Z")

</div>

> [@ForceBru](#):
>
> Since all of these are “invalid usecases”, is there a proper way of extracting the return type of a _future_ function call?

IMO, Julia should embrace type inference and make this function (`promote_op` or `return_type`) fully public and recommended. There’s a lot of code, including many packages, that use it – see [incomplete sample on JuliaHub](https://juliahub.com/ui/Search?type=code&q=Base.promote_op%7CCore.Compiler.return_type&rx=true).  
People use it not because they actively want to use internals, but because this functionality is clearly useful and very hard/impossible to replace with something else.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [September 2, 2025, 8:20pm UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/15 "2025-09-02T20:20:40Z")

</div>

> [@aplavin](#):
>
> Julia should embrace type inference and make this function (`promote_op` or `return_type`) fully public and recommended

> [@aplavin](#):
>
> There’s a lot of code, including many packages, that use it – see [incomplete sample on JuliaHub](https://juliahub.com/ui/Search?type=code&q=Base.promote_op%7CCore.Compiler.return_type&rx=true).

> [@aplavin](#):
>
> this functionality is clearly useful and very hard/impossible to replace with something else

Sure type inference is cool. I opened the relevant feature request myself:

- [[FR] user-facing type inference API · Issue #54207 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/54207)

However I guess most use of type inference that you mention is quite invalid. IMO type inference in user code is certainly not, due to the nature of type inference in Julia as a compiler optimization, a feature that can ever be safe or recommended.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [September 5, 2025, 2:13am UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/16 "2025-09-05T02:13:50Z")

</div>

> [@aplavin](#):
>
> IMO, Julia should embrace type inference and make this function (`promote_op` or `return_type`) fully public and recommended.

The root of the problem is we don’t actually want type inference to be stable. In an ideal world, the compiler narrows down types to the narrowest set of possibilities, and we _could_ use that as a stable feature. But due to practical limitations on implementation, Julia doesn’t do that, and the goal is to further improve type inference in that direction e.g. inferring reassignable variables captured by closures. If we don’t often care what our variable’s types are, then that’s just good optimization. That’s the case in Julia, a dynamically typed language; while we can associate variables with types in some ways and the compiler infers types for expressions, our code ultimately considers the concrete runtime type of the value, e.g. `a::Integer = 1; typeof(a) == Int`. The compiler’s actions are only an implementation detail; a Julia implementation can have no compiler at all, and it could still implement `code_warntype`/`return_types`/`promote_op` independently (they can diverge from the compiler already, [Issue #32834](https://github.com/JuliaLang/julia/issues/32834)).

Bringing an unstable inferred type from compile time into the program at runtime makes the program unstable. For example, a conditional check of an inferred type can fail upon any improvement (or regression, though that should be patched) of the compiler. For another example, instantiating a parametric container like `Vector{T}` with an inferred type can narrow, which will break code designed to insert other elements of the previously wider type. That might a reason for array element type inference being noticeably wider than the compiler’s inference, but I don’t know if that’s semantically stable either, and it’s already not type-stable:

```julia-auto
julia> badone(x::Integer) = (1, 1.0)[1+x%2] # infers as Union{Int, Float64}
badone (generic function with 1 method)

julia> Base.return_types(broadcast, typeof.((badone, 1:1)))
1-element Vector{Any}:
 Union{Vector{Float64}, Vector{Int64}, Vector{Real}}

julia> [eltype(badone.(x)) for x in [1:0, 1:1, 2:2, 1:2]]
4-element Vector{DataType}:
 Real
 Float64
 Int64
 Real

```

I think some sort of return type estimate API would be more useful than having to dig into an opaque set of overlapping internals, but I don’t think any version of it can amount to semantic stability. It really seems like a type is only stable if it was explicitly specified or if it was computed from another type that is, e.g. `eltype(x)` only uses `typeof(x)`, `ndims` dispatches on the type parameters of `x` instead of using the instance directly.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [September 5, 2025, 2:32am UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/17 "2025-09-05T02:32:52Z")

</div>

Note that a `return_type` equivalent can easily be built out of public Julia functions. It has basically the same behavior as `promote_op`, just more overhead:

```julia-auto
julia> return_type(f, T) = eltype(map(f, T[]))
return_type (generic function with 1 method)

julia> return_type(first, Vector{Int})
Int64

julia> return_type(x -> x > 2 ? x : 3., Int)
Real

julia> return_type(x -> x > 2 ? x : 3, Int)
Int64

```

So, inference results are effectively public, and “always” has been.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [September 5, 2025, 1:06pm UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/18 "2025-09-05T13:06:10Z")

</div>

> [@nsajko](#):
>
> So, instead of your imagined `Function{R}`, you can use `ComposedFunction{Base.Fix2{typeof(typeassert), Type{R}}}`. You’d perhaps want a type alias to make it shorter.

A package to make that simpler is EnforcedTypeSignatureCallables.jl. Discourse thread (may wish to scroll down to the v3 announcement):

- [[ANN] EnforcedTypeSignatureCallables.jl - #11 by nsajko](https://discourse.julialang.org/t/ann-enforcedtypesignaturecallables-jl/117280/11)

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [September 5, 2025, 7:43pm UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/19 "2025-09-05T19:43:39Z")

</div>

> [@aplavin](#):
>
> It has basically the same behavior as `promote_op`

It does not, it’s wider for your example:

```julia-auto
julia> return_type(x -> x > 2 ? x : 3., Int)
Real

julia> Base.return_types(x -> x > 2 ? x : 3., (Int,))
1-element Vector{Any}:
 Union{Float64, Int64}

julia> Base.promote_op(x -> x > 2 ? x : 3., Int)
Union{Float64, Int64}

```

`promote_op`’s docstring also warns that it is an undefined upper bound, in other words it can be wider in this way depending on unusual factors like language version, loaded packages, command line options, and who knows what else the docstring didn’t mention.

An undefined type could be fine for crunching numbers interactively, but it’s probably not acceptable for translating anything from _Rust_, a statically typed language where function pointer types are explicitly associated with input and output types. The `map(f, T[])` bit is a source of type instability like `return_types`, so it can defeat the original purpose. A `@generated` function might be able to evaluate those calls at compile time and skip over the instability.

---

<div class="post-metadata">

**Author:** ![minetest2048](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/minetest2048/32/45961_2.png) [@minetest2048](https://discourse.julialang.org/u/minetest2048)\
**Post date:** [September 5, 2025, 9:44pm UTC](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992/20 "2025-09-05T21:44:38Z")

</div>

In rust I can do this:

```rust
fn verify_input_before_function_rust<T>(f:fn(i32) -> T, x:i32) -> Result<T, String>{
    if x != 2 {
        return Err("not two".to_string());
    } else {
        return Ok(f(x));
    }
}

fn main() {
    println!("Hello, world!");

    fn must_pass_two(x:i32) -> i32{
        if x !=2 {
            panic!("not two")
        }
        x+1
    }

    match verify_input_before_function_rust(must_pass_two ,2){
        Ok(o) => println!("Function returns: {}", o),
        Err(o) => println!("Function returns: {}", o)
    }

}

```

I want my `verify_input_before_function` to be generic across function `f` output type. If the compiler infers a type that is too wide or even `Any` my `verify_input_before_function` doesn’t care, same as how normal untyped Julia code doesn’t really care about its return type. As long as whatever its return type is, it is wrapped inside my `Result` type.

But my other goal is for this function to work inside `--trim`, where you won’t have the compiler at runtime to fix up your type shenanigans, so if your passed in function `f` have any type instability shenanigans I assume that `--trim` will also fail.

After some searching I found this: [Ridiculous idea: types from the future](https://discourse.julialang.org/t/ridiculous-idea-types-from-the-future/5457/1) and why using `Base.promote_op` is a bad idea: [Use of promote\_op problematic · Issue #396 · JuliaControl/ControlSystems.jl · GitHub](https://github.com/JuliaControl/ControlSystems.jl/issues/396) and [Crash with MonteCarloMeasurements + ControlSystems · Issue #38436 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/38436#issuecomment-730200157)

[Next page](https://discourse.julialang.org/t/type-stable-base-return-types-is-it-possible/131992.md?page=2)
