julia icon indicating copy to clipboard operation
julia copied to clipboard

RFC: TypedCallable

Open topolarity opened this issue 6 months ago • 5 comments

Introduction

This document proposes a new built-in closure object for efficiently calling a runtime-known function with compile-time known argument types, similar to a C function pointer (but for Julia code).

Users can construct a TypedCallable{AT,RT} from any callable object f and a set of argument + return types:

global logger::TypedCallable{Tuple{String},Nothing} # declare type for type-stability
logger = TypedCallable{Tuple{String},Nothing}(FileLogger()) # set logger dynamically
logger("Test logging message") # calls `(::FileLogger)(::String)`

Calling this object (e.g. typed_callable(args...)) is then a fast equivalent of:

invokelatest(f, (args::AT)...)::RT

i.e. calling f in the latest world with an implied set of argument and return type assertions.

Unlike a typical "dynamic dispatch", calling a TypedCallable can be nearly as fast as a fully static function invocation. The fast execution path is an integer comparison + indirect jump via a function pointer.

It also infers well by default, without additional type assertions.

Full Proposal: https://hackmd.io/@pnwOFwoGSfuDnlKA1M6XaQ/RFC-Julia-TypedCallable

Differences vs. #55111

This is a follow-up to the (now defunct) https://github.com/JuliaLang/julia/pull/55111.

In comparison to that PR, TypedCallable is now a first-class object specially-managed by the Julia runtime. This new design:

  • allows TypedCallable to survive pre-compilation (without becoming a MissingCodeError / segfault to call, in contrast to OpaqueClosure)
  • may not require the JIT at runtime (due to improved serialization support, in contrast to FunctionWrappers)
  • has better code re-use in the runtime, so that a TypedCallable calls the "same" code that a normal call does

Status

Implementation is ongoing.

Proposal is shared here for feedback and to document the rationale behind the upcoming implementation.

topolarity avatar Oct 07 '25 19:10 topolarity

Pleased to see progress on this.

This may be beyond the scope of the design, but would it be possible for method signatures to be part of the type system, such that you can use them for type constraints? At the moment we have Function, which a) says nothing about the methods of the function, and b) isn't applicable to all callables. It would be nice if you could say, for purposes of struct fields or method arguments, that a callable needs to conform to a method signature. This TypedCallable achieves that, but it incurs with it the runtime dispatch overhead.

ancapdev avatar Oct 08 '25 07:10 ancapdev

It would be nice if you could say, for purposes of struct fields or method arguments, that a callable needs to conform to a method signature. This TypedCallable achieves that, but it incurs with it the runtime dispatch overhead.

Can you share an example?

Typically some amount of dispatch overhead (in this case, a function pointer) is unavoidable, unless you know the identity of the Function object which you can already write as ::typeof(foo) today.

topolarity avatar Oct 08 '25 12:10 topolarity

Can you share an example?

Typically some amount of dispatch overhead (in this case, a function pointer) is unavoidable, unless you know the identity of the Function object which you can already write as ::typeof(foo) today.

Sure, in our systems we often have code in the forms:

function f(g, ...)
    ...
    g(...)
    ...
end

or

struct X{H1, H2, ...}
    h1::H1
    h2::H2
    ...
end

function f(x::X, y)
    ...
    if some_condition
        x.h1(y)
    elseif some_other_condition
        x.h2(y)
    end
    ...
end

We build event driven systems where you may have chains of handlers from some root event (e.g., a network packet), and we maintain specific types for all callbacks up the chain, with only type erased functions where we need them (e.g., at the root). It would be a nice feature for us if we could specify the signature for methods these callables have, without wrapping them in an type erased layer like TypedCallable, which requires indirection. It feels to me if the expressive power is there to describe method signatures in types, it would be elegant if we had a type constraint we could apply to say a callable type must provide a method of a signature. I know you could do this with explicit checks to see if methods exists, and I realize since the only way to type constrain currently is through a type hierarchy, the closest we can do is probably using one of the traits implementations. Only musings, I'm sure this falls out of scope of the TypedCallable feature.

ancapdev avatar Oct 08 '25 17:10 ancapdev

 I'm sure this falls out of scope of the TypedCallable feature.

Yeah, this proposal is concerned with performing a fast partially type-erased call - interface proposals are a separate category

topolarity avatar Oct 08 '25 17:10 topolarity

Is it something like std::function in C++?

songjhaha avatar Dec 08 '25 01:12 songjhaha