# ANN: TraitInterfaces.jl - explicit interfaces in Julia

**URL:** <https://discourse.julialang.org/t/ann-traitinterfaces-jl-explicit-interfaces-in-julia/131151>\
**Category:** Package Announcements\
**Tags:** package, announcement, traits, interface\
**Created:** [July 28, 2025, 11:01pm UTC](https://discourse.julialang.org/t/ann-traitinterfaces-jl-explicit-interfaces-in-julia/131151 "2025-07-28T23:01:52Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![kris-brown](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kris-brown/32/206886_2.png) [@kris-brown](https://discourse.julialang.org/u/kris-brown)\
**Post date:** [July 28, 2025, 11:01pm UTC](https://discourse.julialang.org/t/ann-traitinterfaces-jl-explicit-interfaces-in-julia/131151/1 "2025-07-28T23:01:53Z")

</div>

Hi, I’m pleased to share TraitInterfaces.jl. It provides an `@interface` macro to declare interfaces and an `@instance` macro to declare implementations of those interfaces. These implementations are identified with Julia values, which we think of as _traits_. I write a lot more in the [README](https://github.com/AlgebraicJulia/TraitInterfaces.jl), but I’ll excerpt a small piece here. I’ll start by showing the use of these macros in the simplest of cases and then show what the macros expand to.

```julia
struct Sheep 
  naked::Bool 
  name::String 
end

joe = Sheep(true, "Joe")

@interface AnimalInterface′ begin
  Species::TYPE # 'abstract type'
  @import String::TYPE # 'concrete type'
  name(s::Species)::String
  noise(s::Species)::String
end

```

```julia
struct SheepImplsAnimalTrait end
trait = SheepImplsAnimalTrait()

@instance AnimalInterface′{Species=Sheep} [model::SheepImplsAnimalTrait] begin 
  name(s::Sheep)::String = s.name
  noise(s::Sheep)::String = s.naked ? "baaaaah?" : "baaaaah!"
end

@test name[trait](joe) == "Joe" # equivalent to name(Trait(trait), joe)
@test noise[trait](joe) == "baaaaah?"

```

```julia
AnimalInterface′.Meta.@wrapper Animal # type which abstracts implementation details
joe_animal = Animal(joe) 

# error because there is no `@instance Animal [model::Int] ...` 
@test_throws MethodError Animal(100) 

@test name(joe_animal) == "Joe" # equivalent to name[joe]()

```

The `@interface` command first expands to putting the abstract types and operations into the namespace where `@interface` is being declared. Then these are imported into a newly created module:

```julia
function name end 
function noise end 
function Species end 

module AnimalInterface′
  export name, noise, Species
  import ..Foo: name, noise, Species # if the ambient module is Foo
  module Meta
    struct T end # A special type associated with the interface

    # Copy of the Julia data structure that stores the content of the interface
    const theory = Interface(:AnimalInterface′, Judgment[...])

    macro wrapper(n)
      ... # to be explained below
    end
  end
end

```

For convenience, we add `getindex` methods so that we can call `my_operation[implementation](args...)` to avoid requiring explicit `Trait()` wrapping. E.g.:

```julia
Base.getindex(::typeof(name), m::Any) = (args...; kw...) -> name(Trait(m), args...; kw...)
Base.getindex(::typeof(noise), m::Any) = (args...; kw...) -> noise(Trait(m), args...; kw...)

```

The `@implements` code generates the following methods:

```julia
function AnimalInterface′.name(m::Trait{<:SheepImplsAnimalTrait}, s::Sheep)::String
    let model = m.value
        s.name # code that was explicitly written by user appears here
    end
end

function AnimalInterface′.noise(m::Trait{<:SheepImplsAnimalTrait}, s::Sheep)::String
    let model = (m).value
      s.naked ? "baaaaah?" : "baaaaah!"
    end
end

```

It then generates code to check whether the interface has been fully implemented:

```julia
if !(hasmethod(AnimalInterface′.noise, Tuple{Trait{SheepImplsAnimalTrait}, Sheep}))
  throw(MissingMethodImplementation(...))
end
# likewise for `noise`

```

Lastly it stores the information of how this implementation assigned concrete types to the abstract type of the interface:

```julia
impl_type(::SheepImplsAnimalTrait, ::typeof(AnimalInterface′.Species)) = Sheep

```

The `@wrapper` macro generates the following code:

```julia
@struct_hash_equal struct Animal
    val::Any
    types::Dict{Symbol, Type}
    function Animal(x::Any)
        types = try
          Dict(:Species => impl_type(x, AnimalInterface′.Species))
        catch _
          error("Invalid $AnimalInterface′ model: $x")
        end
        new(x, types)
    end
end

Base.get(x::Animal) = x.val
impl_type(x::Animal, o::Symbol) = x.types[o]
AnimalInterface′.noise(x::Animal, args...; kw...) = 
  AnimalInterface′.noise(Trait(x.val), args...; kw...)
AnimalInterface′.name(x::Animal, args...; kw...) = 
  AnimalInterface′.name(Trait(x.val), args...; kw...)

```

There are lots of more features, such as aliases, default methods, extending interfaces, multiple inheritance (e.g. `ThRing` combines `ThAbelianGroup` and `ThMonoid`), declaring types which depend on types and depend on terms, traits which have nontrivial data (e.g. `struct ModuloArith n::Int end`, rather than traits being zero-field structs), traits which have type parameters (e.g. implementing an interface with `Matrix{T} where T`).

I’m not an expert in the other interface / trait libraries in Julia, so it will take some time to draw detailed comparisons. I also noticed just before posting that MultipleInterfaces.jl got announced recently!
