# Zig Like Interfaces for Julia

**URL:** <https://discourse.julialang.org/t/zig-like-interfaces-for-julia/125085>\
**Category:** General Usage\
**Tags:** interface, design-pattern\
**Created:** [January 22, 2025, 4:02pm UTC](https://discourse.julialang.org/t/zig-like-interfaces-for-julia/125085 "2025-01-22T16:02:02Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![cshen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cshen/32/217287_2.png) [@cshen](https://discourse.julialang.org/u/cshen)\
**Post date:** [January 22, 2025, 4:02pm UTC](https://discourse.julialang.org/t/zig-like-interfaces-for-julia/125085/1 "2025-01-22T16:02:02Z")

</div>

I’ve recently come across how Zig handles interfaces. The gist is that Zig doesn’t have syntax sugar for interfaces and instead opts for manually managing the vtable.  
I got curious and started playing with the idea, and I think it translates to julia quite well:

```julia
# An interface is simply a struct that contains a list of function pointers
writer(x) = error("$(typeof(x)) does not implement the Writer interface.")
struct Writer{T}
    impl::T
    writeAll::Function
    writeSome::Union{Nothing,Function}
    about::Function
end
# The signature of these functions is encoded in these definitions
writeAll(w::Writer, data::String) = w.writeAll(w.impl, data)
writeSome(w::Writer, data::String) = w.writeSome(w.impl, data)
about(w::Writer) = println("\nI'm a writer! of subtype $(typeof(w.impl))\n")

# we can write custom Initializers to include functions always available to an interface:
Writer(x, writeAll, writeSome = nothing) = Writer(x, writeAll, writeSome, about)

# We can define functionality that assumes a method is available
# And this is enforced by the fact that a Writer object must be
# initialized with the needed functions.
complex_functions(x, data) = complex_functions(writer(x), data)
function complex_functions(w::Writer, data)
    about(w)

    # w will always have a writeAll function
    writeAll(w, data)

    # we can also check if this particular implementation has
    # defined the optional writeSome.
    isnothing(w.writeSome) || writeSome(w, data)

    return nothing
end

# Then we can implement this interface on different types
struct File
    fd::Int
end
writeAll(f::File, data) = println("value: ", f.fd, " msg: ", data)
# we could also pass existing functions that act on File, 
# not necessarily the writeAll, as long as it accept the correct inputs
writer(f::File) = Writer(f, writeAll)

# or another implementer:
struct StrangeFile
    fd::Float64
end
writeAll(f::StrangeFile, data) = println("value: ", f.fd, " msg: -VERY STRANGE-", data)
writeSome(f::StrangeFile, data) = println("value: ", f.fd, " msg: -OPTIONAL METHOD-", data)
writer(f::StrangeFile) = Writer(f, writeAll, writeSome)

# or yet another, simpler one
writeAll(i::Int, data) = println("value: ", i, " msg: -I'm an Int-", data)
writer(i::Int) = Writer(i, writeAll)

# And it just works(TM) :D
f = File(1)
sf = StrangeFile(1.0)
f = File(1)
sf = StrangeFile(1.0)

complex_functions(f, "hello interfaces")
# I'm a writer! of subtype File
#
# value: 1 msg: hello interfaces

complex_functions(sf, "hello interfaces")
# I'm a writer! of subtype StrangeFile
#
# value: 1.0 msg: -VERY STRANGE-hello interfaces
# value: 1.0 msg: -OPTIONAL METHOD-hello interfaces

complex_functions(10, "hello interfaces")
# I'm a writer! of subtype Int64
#
# value: 10 msg: -I'm an Int-hello interfaces

complex_functions(1.0, "Not supported")
# ERROR: Float64 does not implement the Writer interface.
# Stacktrace:
# ...

```

I don’t know if this is something that is already well known, and if it is I apologise for the noise, but I thought this was an interesting design pattern, reminiscent of holy traits.  
You could argue, “congratulation for figuring out polymorphism”, but I think that the valuable part is having to fill all the gaps when instantiating the interface object, which gives you the extra assurance that a method exists when dispatching on it.  
Also from my limited testing, ti seems most of the machinery is compiled away.

However, I am not knowledgeable enough about the internals to figure out why this design pattern could be a bad idea, or where it could break, or what are the edgecases. So I aim to leverage [Cunningham’s Law](https://meta.wikimedia.org/wiki/Cunningham%27s_Law) with this post and leech some knowledge 🙂 .

---

<div class="post-metadata">

**Author:** ![simsurace](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/simsurace/32/30216_2.png) [@simsurace](https://discourse.julialang.org/u/simsurace)\
**Post date:** [January 22, 2025, 6:40pm UTC](https://discourse.julialang.org/t/zig-like-interfaces-for-julia/125085/2 "2025-01-22T18:40:57Z")

</div>

Interesting approach. How would you constrain call signatures? IIUC, this design would not prevent storing inappropriate functions. Would you combine this with something like FunctionWrappers?

---

<div class="post-metadata">

**Author:** ![cshen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cshen/32/217287_2.png) [@cshen](https://discourse.julialang.org/u/cshen)\
**Post date:** [January 22, 2025, 7:23pm UTC](https://discourse.julialang.org/t/zig-like-interfaces-for-julia/125085/3 "2025-01-22T19:23:39Z")

</div>

As it is right now yes, you can’t prevent storing functions with the wrong signature.  
You’ll most likely encounter a method error once called (defeats the purpose a bit yeah). In the end whatever function you pass in, it gets called with the type signature defined in the interface

```julia
writeAll(w::Writer, data::String) = w.writeAll(w.impl::T, data::String)
# that `w.writeAll` is the function passed to the interface

```

I am not familiar with FunctionWrappers, but having something that would allow for:

```julia
struct Writer{T}
    impl::T
    writeAll::Function{T, String} #signature writeAll(::T, ::String)
    writeSome::Union{Nothing,Function{T, String}
    about::Function{Writer}
end

```

Would be awesome, yes. That `about::Function{Writer}` contains a self reference, which might be problematic. But then again, that is the signature only for the functions that are common to all implementations of the interface, so the caller doen’t really touch that. So it can just be `about::Function` i guess.

Thanks for pointing FunctionWrappers.jl out, i’ll definitely give it a go!

---

<div class="post-metadata">

**Author:** ![cshen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cshen/32/217287_2.png) [@cshen](https://discourse.julialang.org/u/cshen)\
**Post date:** [February 3, 2025, 10:26pm UTC](https://discourse.julialang.org/t/zig-like-interfaces-for-julia/125085/4 "2025-02-03T22:26:24Z")

</div>

I had a spare evening and I played a bit with the FunctionWrappers.jl and had interesting results  
Interface Definition:

```julia
using FunctionWrappers

writer(x) = error("$(typeof(x)) does not implement the Writer interface.")
struct Writer{T}
    impl::T

    writeAll::FunctionWrappers.FunctionWrapper{Nothing,Tuple{T,String}}

    writeSome::Union{Nothing,FunctionWrappers.FunctionWrapper{Nothing,Tuple{T,String}}}

    about::FunctionWrappers.FunctionWrapper{Nothing, Tuple{}}

    function Writer(x;
        writeAll,
        writeSome = nothing
    )
        T = typeof(x)

        # short form
        writeAll_wrapper = fieldtype(Writer{T}, :writeAll)(writeAll)

        # generic form, should work for all fields.
        writeSome_t = fieldtype(Writer{T}, :writeSome)
        if isnothing(writeSome)
            writeSome_wrapper = writeSome_t <: Union{Nothing, V} where {V} ?
                writeSome_wrapper = nothing : throw(ArgumentError("writeSome is not Optional."))
        else
            writeSome_wrapper = Base.nonnothingtype(writeSome_t)(writeSome)
        end

        about() = println("\nI'm a writer! of subtype $T\n")
        about_wrapper = fieldtype(Writer{T}, :about)(about)

        return new{T}(x, writeAll_wrapper, writeSome_wrapper, about_wrapper)
    end
end
writeAll(w::Writer, data::String) = w.writeAll(w.impl, data)
writeSome(w::Writer, data::String) = w.writeSome(w.impl, data)

```

Now This is all a bit of a handfull to write, but I wonder how much of it can be automated inside a macro like or something along these lines.

```julia
@interface Writer{T} begin
    writeAll{T, String}::Nothing

    @optional writeSome{T, String}::Nothing

    @shared about{}::Nothing = begin
        println("\nI'm a writer! of subtype $T\n")
    end
end

```

Then, some usage based on the interface can be defined:

```julia
function_using_writer(x, data) = function_using_writer(writer(x), data)

function function_using_writer(w::Writer, data)
    w.about() # shared interface function are accssesed as properties.
    writeAll(w, data)
    isnothing(w.writeSome) || writeSome(w, data)

    nothing
end

```

And an interface implementer (implementing is just a matter of being able to construct the `Writer` struct)

```julia
struct File
    fd::Int
end
file_speficif_write(f::File, data::String) = println("value: ", f.fd, " msg: ", data)
writer(f::File) = Writer(f; writeAll=file_specific_write)

```

and it just works ™

```julia-repl
julia> f = File(1)
File(1)

julia> function_using_writer(f, "hello interfaces")

I'm a writer! of subtype File

value: 1 msg: hello interfaces

```

And the type signature is enforced (at runtime though)  
EDIT: Is there a way to make a function wrapper throw with the wrong signature upon construction?

```julia
struct BadFile
    fd::Int
end
bad_writeAll(f::BadFile, data::Vector{String}) = println("value: ", f.fd, " msg: ", data[1])
writer(f::BadFile) = Writer(f; writeAll=bad_writeAll)

```

```julia-repl

julia> bf = BadFile(1)
BadFile(1)

julia> complex_functions(bf, "hello interfaces")

I'm a writer! of subtype BadFile

ERROR: MethodError: no method matching bad_writeAll(::BadFile, ::String)
The function `bad_writeAll` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  bad_writeAll(::BadFile, ::Vector{String})
   @ Main REPL[24]:1

Stacktrace:

```

Another great extra is that the interface is self documenting through normal LSP functions:

- `Writer(...` completion for the name of the methods. and which are optional
- `Writer(x; writeAll(...` you can add a `(` instead of a `=` to peek at the generic signature of the `writeAll` method. In my editor it gives me `writeAll(w::Writer, data::String)` and this generic method is part of the interface.

Performance wise, probably bad, it’s dynamic dispatch galore.
