# Traits for Static Dispatch

**URL:** <https://ziggit.dev/t/traits-for-static-dispatch/10819>\
**Category:** Brainstorming\
**Created:** [July 5, 2025, 11:02pm UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819 "2025-07-05T23:02:25Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![lipfang](https://ziggit.dev/user_avatar/ziggit.dev/lipfang/32/9809_2.png) [@lipfang](https://ziggit.dev/u/lipfang)\
**Post date:** [July 5, 2025, 11:02pm UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/1 "2025-07-05T23:02:25Z")

</div>

I decided to write up a quick proposal for static-dispatched traits, I think there are several problems with `anytype` and I’m hoping that this explains some of my problems with it as well as how they can possibly be solved.

I’ll apologize ahead of time for the 80-character linebreaks which might be a bit frustrating to read on web, I had written this in a text editor ahead-of-time 😅

# Problems with Current Solutions

There are two main ways to do static dispatching in Zig:

1. `somevar: anytype`
2. `fn myFn(comptime T: type, somevar: T)`

Both of these suffer from the same issue: there is no named type  
associated with the identifier being defined.

Because there is no named type constraint for the identifier,  
a beginner would never know what to pass in to `writer: anytype`,  
but if it could be like `writer: std.io.Writer`, it’s much more clear (as well  
as there now being documentation for what `std.io.Writer` _is_). This also  
helps in large codebases where there may be an important difference between  
different kinds of writers, and there’s no way to separate  
`special_writer: anytype` and `io_writer: anytype` (where with traits,  
this could be `writer: mylib.Writer` and `writer: std.io.Writer`).

In addition to poor documentation, `anytype` also runs into the issue of having  
pretty awful error messages. Accidentally passing a `BufferedWriter`  
or `File` to a library which takes a `writer: anytype`  
may give an error message that relates to a deeply-nested file within  
the library’s code. It would be great to have a solution which tells the caller  
that they are simply calling the function incorrectly.

# Static Dispatch: Trait Types

The goal of Trait Types is to allow an interface-like type which  
can have reusable types, documentation, and better error messages.

Trait types may only be used in the same places as `anytype`.  
For instance, `fn (x: SomeTrait) void` is legal, but  
`const x: SomeTrait = 5` is not.

A type is assignable to a given trait type  
if it contains the same fields and public declarations that the trait has.

## Defining and using Traits

For instance, we could have a `std.io.Writer` interface defined as:

```zig
/// A standard writer interface which allows for statically-dispatched writers.
/// See `GenericWriter` for a way to implement this trait using only a `write` function.
const Writer = trait {
    pub const Context;
    pub const WriteError;
    pub fn write(self: Context, buf: []const u8) WriteError!usize;
    pub fn writeAll(self: Context, buf: []const u8) WriteError!void;
    // ...
};

```

(I’m aware that `std.io.Writer` is going to be entirely  
reworked for `std.Io`, but I’m using it as an example since  
it’s one of the most widely used cases for static dispatch `anytype`)

Aside - I have also taken a liking to naming this `anytype`  
(ie: `const Writer = anytype { pub const ... }`).  
This makes it much more clear that this is essentially just a  
restricted form of `anytype` (and the feature could  
be called “anytype constraints” instead of “traits”)

Implementing this trait would then look like:

```zig
// this is implementing `Writer` from scratch; in reality you'd use `GenericWriter`.
const MyThingWriter = struct {
    pub const Context = *MyThingWriter;
    pub const WriteError = error{ SomeError };
    pub fn write(self: *MyThingWriter, buf: []const u8) WriteError!usize {
        // ...
    }
    pub fn writeAll(self: *MyThingWriter, buf: []const u8) WriteError!void {
        // ...
    }
};

// and GenericWriter could look like:
pub fn GenericWriter(
    comptime ContextT: type,
    comptime WriteErrorT: type,
    comptime writeFn: fn (context: Context, bytes: []const u8) WriteError!usize,
) type {
    return struct {
        pub const Context = ContextT;
        pub const WriteError = WriteErrorT;

        pub inline fn write(context: Context, bytes: []const u8) WriteError!usize {
            return writeFn(self.context, bytes);
        }
        // ...
    };
}

```

This makes it so that now functions which used to be defined  
as `fn writeMyThing(myThing: MyThing, writer: anytype) !void` can become  
`fn writeMyThing(myThing: MyThing, writer: std.io.Writer) !void`,  
and it would work the exact same.

This would also restrict type bounds at the call-site of `writeMything`,  
rather than inside of a deeply-nested file in library code.

One thing to take note of is that traits are implicitly implemented  
(really, traits aren’t “implemented” at all - they’re  
just type-bounds for functions). However, it may be worth adding a  
way to explicitly implement an interface with a function like  
`try std.testing.expectImplements(MyType, SomeTrait)`, or even  
an explicit `implementing` keyword on struct definitions.

---

<div class="post-metadata">

**Author:** ![permutationlock](https://ziggit.dev/user_avatar/ziggit.dev/permutationlock/32/9958_2.png) [@permutationlock](https://ziggit.dev/u/permutationlock)\
**Post date:** [July 6, 2025, 1:52am UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/2 "2025-07-06T01:52:30Z")

</div>

Have you seen any of the GitHub issue discussions related to this? The four big threads I remember are [1268](https://github.com/ziglang/zig/issues/1268), [1669](https://github.com/ziglang/zig/issues/1669), [6615](https://github.com/ziglang/zig/issues/6615), and [17198](https://github.com/ziglang/zig/issues/17198), but there were more.

---

<div class="post-metadata">

**Author:** ![dimdin](https://ziggit.dev/user_avatar/ziggit.dev/dimdin/32/1457_2.png) [@dimdin](https://ziggit.dev/u/dimdin)\
**Post date:** [July 6, 2025, 4:21am UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/3 "2025-07-06T04:21:12Z")

</div>

I believe that there is real benefit to the traits as defined, for documentation and editors code completion. A similar design might omit the `self` arguments in traits.

Andrew response to a similar proposal for static dispatch interfaces was: [comptime interfaces · Issue #1268 · ziglang/zig · GitHub](https://github.com/ziglang/zig/issues/1268#issuecomment-1570922291)

> I’m not saying there won’t be interfaces of any kind, ever, but there are no current plans for adding them, …  
> I suggest if you want to use zig, you accept the fact that it may never gain an interface-like feature, and also trust the core team to add it in a satisfactory manner if it does get added at all.

---

<div class="post-metadata">

**Author:** ![ajoino](https://ziggit.dev/user_avatar/ziggit.dev/ajoino/32/3120_2.png) [@ajoino](https://ziggit.dev/u/ajoino)\
**Post date:** [July 6, 2025, 10:16am UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/4 "2025-07-06T10:16:46Z")

</div>

I think one can accept that a change might never happen while still lobbying for that change. I think OP is 100% correct that `anytype` is too generic and having a way to put constraints on the type would be a great improvement for the readability of the language.

---

<div class="post-metadata">

**Author:** ![ericlang](https://ziggit.dev/user_avatar/ziggit.dev/ericlang/32/8515_2.png) [@ericlang](https://ziggit.dev/u/ericlang)\
**Post date:** [July 6, 2025, 12:03pm UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/5 "2025-07-06T12:03:18Z")

</div>

While agreeing something maybe should change regarding anytype / interfaces, I also think flexibility should be preserved. Currently we can literally just put anything in an anytype and the compiler figures it out.  
What if anytype should be a `u8` or `u16`? Should we have a trait for that?

I think it is a very very difficult problem. Before we know we would get into Rust-like unreadable stuff. I cannot visualize any good + simple + readable syntactical solution better than the current one with anytype.

---

<div class="post-metadata">

**Author:** ![Verafahn](https://ziggit.dev/user_avatar/ziggit.dev/verafahn/32/9285_2.png) [@Verafahn](https://ziggit.dev/u/Verafahn)\
**Post date:** [May 18, 2026, 9:41am UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/6 "2026-05-18T09:41:09Z")

</div>

If [#32099](https://codeberg.org/ziglang/zig/issues/32099) is accepted, I think the following options are worth considering.

Allow `|T: trait|` syntax. When the inferred `T` does not satisfy `trait`, it leads to compilation errors. I think this approach is more intuitive than simply writing `trait` in the type position.

I have thought of using many keywords to define the keywords for this container: `interface`/`anytype`(from Zig)/`trait`(from Rust)/`concept`(from C++)…I’ll call it `trait` for now.

`trait` should be able to perform type merge operations like `error`. That is to say, in `|T: trait1 || trait2|`, `T` satisfies both requirements `trait1` and `trait2`. But, semantically speaking, `&&` may be more suitable.

`type` can be regarded as an unconstrained `trait`. Or, `trait` is a subset of `type`. Just like the relationship between `error` and `anyerror`. `trait` is more like types of type.

I think clear boundaries are more important than false freedom. `anytype` allows you to pass any type to parameters, but this does not mean that the API can accept all types.

---

<div class="post-metadata">

**Author:** ![Plebosaur](https://ziggit.dev/user_avatar/ziggit.dev/plebosaur/32/6857_2.png) [@Plebosaur](https://ziggit.dev/u/Plebosaur)\
**Post date:** [May 18, 2026, 2:25pm UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/7 "2026-05-18T14:25:31Z")

</div>

I would personally really like use something like that, as my code has a lot ducktyped anytype code unfortunately.  
I could add safety checks with reflection but what ends up happening is that I decide it would take too much time and avoid it.  
I think something like a generic that returns a trait would be useful. (Example, Iterator(T) would be a function that returns a trait, and that trait has a next() function that returns ?T etc. On that topic, I tried making a userland implementation of comptime interfaces and it ended up being pretty bad).

It would also solve the Io regressions (Binary size + mandatory VTable indirection). However it could be the case that this feature is redundant when the core team solves these regressions (with potentially a different mechanism)

Personally, as a user of zig I am really concerned with trying to write optimal machine code (And I chose zig because more so than any other language it cares about that), and things like that would really reduce friction when trying to write optimal machine code. We make fun of OOP for putting VTables everywhere for things that are comptime known.

---

<div class="post-metadata">

**Author:** ![Dok8tavo](https://ziggit.dev/user_avatar/ziggit.dev/dok8tavo/32/707_2.png) [@Dok8tavo](https://ziggit.dev/u/Dok8tavo)\
**Post date:** [May 18, 2026, 3:26pm UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/8 "2026-05-18T15:26:07Z")

</div>

I guess the replacement of `anytype` would be a good opportunity to bring those `trait`s.

I’ve seen a few different kinds of proposals though.

1. They could be a sort of type-matching for structural types:

```zig
generic_pointer: *|T|,

```

1. They could also be about the definition location of non-structural types, to work well with generic types:

```zig
array_list: std.ArrayList(Dummy |Item|),

```

The `Dummy` is a concrete type that indicates that the definition of `std.ArrayList(Item)` should be that of `std.ArrayList(Dummy)`.

1. Or we could use a new `trait {}` construct.

2. Or define traits as an interface in `std.builtin` like this:

```zig
pub const Trait = struct {
    result: fn (comptime type) Result,

    pub fn assert(comptime trait: Trait, comptime T: type) void {
        comptime if (trait.result(T).fail()) |failure|
            @compileError(std.fmt.comptimePrint("{f}", .{failure});
    }
};

```

And then `generic: |T: trait|` would just be sugar for `generic: |T|` and inserting `trait.assert(T)`.

---

<div class="post-metadata">

**Author:** ![kristoff](https://ziggit.dev/user_avatar/ziggit.dev/kristoff/32/9_2.png) [@kristoff](https://ziggit.dev/u/kristoff)\
**Post date:** [May 18, 2026, 3:33pm UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/9 "2026-05-18T15:33:15Z")

</div>

I would caution against interpreting #32099 as opening the door to parametric polymorphism. The syntax might make it seem a relatively small leap, but it’s incompatible with how the type system currently works and other parametric polymorphism proposals have already been rejected.

---

<div class="post-metadata">

**Author:** ![KilianHanich](https://ziggit.dev/user_avatar/ziggit.dev/kilianhanich/32/8254_2.png) [@KilianHanich](https://ziggit.dev/u/KilianHanich)\
**Post date:** [May 18, 2026, 4:38pm UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/10 "2026-05-18T16:38:07Z")

</div>

Yeah, but as things are (and with how Zig tries to position itself), the request for something like this will be a constant in Zig’s lifetime until something like this is either added or Zig is barely used anymore.

People like compile time polymorphism while idiomatic Zig seems to prefer runtime polymorphism these days for more complex stuff (at least if we go by the last two releases), but with `anytype` (or the pattern of `std.mem.eql`) you have unconstrained duck-typing which isn’t really liked in compiled languages by many people.

To make a comparison with C++:

`anytype` is like

```C++
auto add(auto a, decltype(a) b) {
    return a + b;
}

```

but people would like to have something like

```C++
auto add(Arithmetic auto a, decltype(a) b) {
    return a + b;
}

```

And I can understand it. Error messages from this without constraints are just garbage if the program becomes complicated.

And before C++ concepts I have seen people write custom DSLs because of that since they were forced to decide between garbage error message when they do something wrong, suboptimal compilation output, or code duplication.

Maybe some construct which receives an expression (before type checking) and returns if it would pass type checking would work? So something like this:

```Zig
fn add(T: type, a: T, b: T) T {
    if (@requires(a + b)) {
        return a + b;
    }
    else if (@requires(@as(T, a.add(b)))) {
        return a.add(b);
    }
    else {
        @compileError(std.fmt.comptimePrint("{s} doesn't support normal addition without errors", .{@typeName(T)}));
    }
}

```

Sure, one would need to write the usages out manually, but the same would happen if you would need to do that in a comment.

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [May 18, 2026, 4:52pm UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/11 "2026-05-18T16:52:41Z")

</div>

You can view it as extension of the type system, but you can also view it as constraining anytype. Traits would really just become comptime asserts that are standardized so that tools like ZLS can read them. That would be a huge benefit to the language. And the concrete type system can stay basically unchanged. Currently, restricted function pointers are being implemented, for something that could easily be comptime known, if we had better DX for anytype.

---

<div class="post-metadata">

**Author:** ![ericlang](https://ziggit.dev/user_avatar/ziggit.dev/ericlang/32/8515_2.png) [@ericlang](https://ziggit.dev/u/ericlang)\
**Post date:** [May 18, 2026, 5:33pm UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/12 "2026-05-18T17:33:27Z")

</div>

I skimmed through the text about `|T|`.  
If I understand correctly `fn (x: |T|, y: |T|) void` would mean x and y are of one and the same type?  
That is already a better distinction than `anytype`…

---

<div class="post-metadata">

**Author:** ![kristoff](https://ziggit.dev/user_avatar/ziggit.dev/kristoff/32/9_2.png) [@kristoff](https://ziggit.dev/u/kristoff)\
**Post date:** [May 18, 2026, 5:41pm UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/13 "2026-05-18T17:41:16Z")

</div>

well that in today’s zig would be one of:

- `fn (T: type, x: T, y: T) void`
- `fn (x: anytype, y: @TypeOf(x)) void`

---

<div class="post-metadata">

**Author:** ![LucasSantos91](https://ziggit.dev/user_avatar/ziggit.dev/lucassantos91/32/440_2.png) [@LucasSantos91](https://ziggit.dev/u/LucasSantos91)\
**Post date:** [May 18, 2026, 6:10pm UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/14 "2026-05-18T18:10:49Z")

</div>

> [@ericlang](#):
>
> If I understand correctly `fn (x: |T|, y: |T|) void` would mean x and y are of one and the same type?

I don’t think so, because you’d be capturing the type `T` twice, which would be a name collision. Once the type is captured, you’re free to use it. I think what you were looking for is `fn (x: |T|, y: T) void`

---

<div class="post-metadata">

**Author:** ![ericlang](https://ziggit.dev/user_avatar/ziggit.dev/ericlang/32/8515_2.png) [@ericlang](https://ziggit.dev/u/ericlang)\
**Post date:** [May 18, 2026, 6:33pm UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/15 "2026-05-18T18:33:53Z")

</div>

Ok… understood

---

<div class="post-metadata">

**Author:** ![Verafahn](https://ziggit.dev/user_avatar/ziggit.dev/verafahn/32/9285_2.png) [@Verafahn](https://ziggit.dev/u/Verafahn)\
**Post date:** [May 19, 2026, 8:17am UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/16 "2026-05-19T08:17:06Z")

</div>

To be precise, this is not parametric polymorphism, but rather a restriction on the parameters to indicate what is acceptable for `anytype` .

> I think clear boundaries are more important than false freedom. anytype allows you to pass any type to parameters, but this does not mean that the API can accept all types.

---

<div class="post-metadata">

**Author:** ![npc1054657282](https://ziggit.dev/user_avatar/ziggit.dev/npc1054657282/32/5790_2.png) [@npc1054657282](https://ziggit.dev/u/npc1054657282)\
**Post date:** [May 19, 2026, 8:36am UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/17 "2026-05-19T08:36:36Z")

</div>

> [@Verafahn](#):
>
> Allow `|T: trait|` syntax. When the inferred `T` does not satisfy `trait`, it leads to compilation errors. I think this approach is more intuitive than simply writing `trait` in the type position.

The new grammar is unnecessary. Just:

```zig
fn (arg: |T|) R: {
    if (!satisfySomeTrait(T)) comptime unreachable;
    break :R T;
} {
    ...
}
```

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [May 19, 2026, 8:38am UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/18 "2026-05-19T08:38:28Z")

</div>

In my view, this is similar to type hints in Python, where there are concrete types, but also `typing` concepts, that are about constraining duck typing. Except that in the case of Zig traits, they would be enforced by the compiler, not an external tool. Both “type” systems can coexists in an single language.

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [May 19, 2026, 8:39am UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/19 "2026-05-19T08:39:51Z")

</div>

If it’s not codified by the language, different projects will use different rules, and we can’t have proper DX, because ZLS can’t parse all the variations. It would be great if it evaluated comptime, but it does not and it’s unlikely it will.

---

<div class="post-metadata">

**Author:** ![npc1054657282](https://ziggit.dev/user_avatar/ziggit.dev/npc1054657282/32/5790_2.png) [@npc1054657282](https://ziggit.dev/u/npc1054657282)\
**Post date:** [May 19, 2026, 8:43am UTC](https://ziggit.dev/t/traits-for-static-dispatch/10819/20 "2026-05-19T08:43:56Z")

</div>

Syntax conventions that only target individual parameters can fall into local optima and cannot express compile-time constraint relationships among all parameters.

ZLS is still very primitive at the moment, and we cannot assume that it will always remain in its current state in the future. [#615](https://github.com/ziglang/zig/issues/615) may bring some changes in the future.

[Next page](https://ziggit.dev/t/traits-for-static-dispatch/10819.md?page=2)
