# We Have Traits at Home - Blog Post

**URL:** <https://ziggit.dev/t/we-have-traits-at-home-blog-post/18059>\
**Category:** Media\
**Created:** [October 22, 2026, 8:08pm UTC](https://ziggit.dev/t/we-have-traits-at-home-blog-post/18059 "2026-10-22T20:08:10Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![axelmagn](https://ziggit.dev/user_avatar/ziggit.dev/axelmagn/32/4732_2.png) [@axelmagn](https://ziggit.dev/u/axelmagn)\
**Post date:** [October 22, 2026, 8:08pm UTC](https://ziggit.dev/t/we-have-traits-at-home-blog-post/18059/1 "2026-10-22T20:08:10Z")

</div>

[We Have Traits at Home](https://www.axelmagn.com/blog/202610-zig-traits/)

I was noodling for a while with some functional programming languages, and in the process I stumbled onto a rather nice way to handle comptime abstraction. After using it in a few places, I thought I would type up a blog post and share.

The gist: you can imitate OCaml modules or Rust traits at comptime, and it can be a nice way to make your function interfaces more explicit without involving VTable machinery.

My blog is fairly young, and I’m thinking of writing more articles in this direction, so any feedback is very much appreciated. Let me know what you think, and if you would be interested in more articles that expand on this idea.

---

<div class="post-metadata">

**Author:** ![MidasVanVeen](https://ziggit.dev/user_avatar/ziggit.dev/midasvanveen/32/9914_2.png) [@MidasVanVeen](https://ziggit.dev/u/MidasVanVeen)\
**Post date:** [October 22, 2026, 8:57pm UTC](https://ziggit.dev/t/we-have-traits-at-home-blog-post/18059/2 "2026-10-22T20:57:18Z")

</div>

Very cool from a technical perspective, I probably won’t use it in practice though. I can imagine that if I did, whoever has to read my code would have a hard time. I’ll just try to forget about traits until they become a language feature

---

<div class="post-metadata">

**Author:** ![Tekla](https://ziggit.dev/user_avatar/ziggit.dev/tekla/32/8985_2.png) [@Tekla](https://ziggit.dev/u/Tekla)\
**Post date:** [October 22, 2026, 10:13pm UTC](https://ziggit.dev/t/we-have-traits-at-home-blog-post/18059/3 "2026-10-22T22:13:23Z")

</div>

Lovely read! Had to stop at **Trait Functors** since I’m way too sleepy to continue, but you really made me think hard about interfaces. I’ll resume asap :3 but I got some thoughts to share.

* * *

Personally, I don’t like Polymorphism. Working in the game industry I have seen all and every sin you can conceive. From the legendary `MasterMainObjectClass`, parent of many many many grandchildren; to hundreds of single function interfaces painstakingly inherited, one by one, by tens of victims; or finding the answer to the question “How many MonoBehaviours can a GameObject hold?”.

Yet, I think Polymorphism is undeniably useful and maybe even necessary the moment you implement generics in your language.

It is my opinion that Zig should’t reach 1.0 without some sort of idiomatic way of doing interfaces. Not idiomatic in the sense of “we all agree to write all this boilerplate” but an actual _ **elegant** _ way of solving Polymorphism.

Some might disagree, but `zig zen` got my back on this one:

```
* Only one obvious way to do things.

```

Right now it’s all but obvious to me. I can count three methods now but there might be more for all I know, which is not much.

I really like your approach btw! It’s the most obvious one to me and if ZLS is not complaining, I’m not complaining.

But in the long run I’d be inclined to go for VTables because that’s what I see each time I inspect the standard library, thus I asume that’s the idiomatic way, and maybe even the only way that survives the Zen doctrine whenever the team starts applying it.

Future is uncertain, as always, and I do trust the community and the team to find the most ~~elegant~~ performant solution of all.

---

<div class="post-metadata">

**Author:** ![Hastur](https://ziggit.dev/user_avatar/ziggit.dev/hastur/32/10051_2.png) [@Hastur](https://ziggit.dev/u/Hastur)\
**Post date:** [October 23, 2026, 3:33am UTC](https://ziggit.dev/t/we-have-traits-at-home-blog-post/18059/4 "2026-10-23T03:33:45Z")

</div>

It was an interesting read.(It reminded me a little of C++ template metaprogramming 😉)

I don’t think it’ll be much of an issue, but since Zig’s behavior can change significantly between versions, I thought it might be helpful to mention which version you have in mind.

---

<div class="post-metadata">

**Author:** ![vulpesx](https://ziggit.dev/user_avatar/ziggit.dev/vulpesx/32/3989_2.png) [@vulpesx](https://ziggit.dev/u/vulpesx)\
**Post date:** [October 23, 2026, 4:39am UTC](https://ziggit.dev/t/we-have-traits-at-home-blog-post/18059/5 "2026-10-23T04:39:21Z")

</div>

One thing I want to point out is, instead of having a separate `builtinNum` (and similar) functions for “trait” implementations, you could just put that a declaration on the “trait”:

`mobius(f32, builtinNum(f32), x, a, b, c, d);` → `mobious(f32, .builtin_num, x, a, b, c, d);`

---

<div class="post-metadata">

**Author:** ![axelmagn](https://ziggit.dev/user_avatar/ziggit.dev/axelmagn/32/4732_2.png) [@axelmagn](https://ziggit.dev/u/axelmagn)\
**Post date:** [October 23, 2026, 4:40am UTC](https://ziggit.dev/t/we-have-traits-at-home-blog-post/18059/6 "2026-10-23T04:40:35Z")

</div>

> I don’t think it’ll be much of an issue, but since Zig’s behavior can change significantly between versions, I thought it might be helpful to mention which version you have in mind.

Good call. I added a note (Zig 0.17).

I know that type captures may change significantly in the future, but I was pleased to find a way to carry on with the current syntax.

> One thing I want to point out is, instead of having a separate `builtinNum` (and similar) functions for “trait” implementations, you could just put that a declaration on the “trait”:
> 
> `mobius(f32, builtinNum(f32), x, a, b, c, d);` → `mobious(f32, .builtin_num, x, a, b, c, d);`

Yep! There are a lot of things like that in the example code, because I felt it was important to show how new functions could build on existing traits. In practice, if you are authoring both the trait and the consumer, you can pack almost everything into the trait definition.

I had to cut this article down quite a lot - previous drafts were 3x as long. It was really hard to figure out what to keep.

---

<div class="post-metadata">

**Author:** ![squeek502](https://ziggit.dev/user_avatar/ziggit.dev/squeek502/32/409_2.png) [@squeek502](https://ziggit.dev/u/squeek502)\
**Post date:** [October 23, 2026, 6:24am UTC](https://ziggit.dev/t/we-have-traits-at-home-blog-post/18059/7 "2026-10-23T06:24:28Z")

</div>

Minor aside: `[*c]T` should never appear outside of auto-translated C code. When writing code that needs to conform to the C ABI, you should make the choice between `[*]T` and `?[*]T`. See [#2984](https://github.com/ziglang/zig/issues/2984) and [#36082](https://codeberg.org/ziglang/zig/issues/36082) for context.

---

<div class="post-metadata">

**Author:** ![Travis](https://ziggit.dev/letter_avatar_proxy/v4/letter/t/bbce88/32.png) [@Travis](https://ziggit.dev/u/Travis)\
**Post date:** [October 23, 2026, 8:22am UTC](https://ziggit.dev/t/we-have-traits-at-home-blog-post/18059/8 "2026-10-23T08:22:58Z")

</div>

Interesting read. I find myself wondering why i’d ever use this. The mobius example seemed a bit contrived or abstract. I’m wondering how it might look in more of a concrete use case.

Maybe something like this search suggestion (in the most popular language of the week 😂)?

EDIT: anyone mind showing how would this look in zig with a runnable test?

```php
<?php
trait Loggable
{
    private function log(string $msg): void
    {
    }
}

trait Notifiable
{
    public function notify(string $subject, string $body): void
    {
    }
}

class User
{
    use Loggable, Notifiable;
    private string $email;
    private string $name;

    public function __construct(string $name, string $email)
    {
        $this->log("Created user {$this->name}");
    }

    public function changeEmail(string $newEmail): void
    {
        $this->log("Email changed from $old to $newEmail");
        $this->notify(
            "Email updated",
            "Hi {$this->name}, your email was changed to $newEmail."
        );
    }
}

```

---

<div class="post-metadata">

**Author:** ![matklad](https://ziggit.dev/user_avatar/ziggit.dev/matklad/32/2462_2.png) [@matklad](https://ziggit.dev/u/matklad)\
**Post date:** [October 23, 2026, 10:36am UTC](https://ziggit.dev/t/we-have-traits-at-home-blog-post/18059/9 "2026-10-23T10:36:22Z")

</div>

```zig
fn mobius(T: type, num: Num(T), x: T, a: T, b: T, c: T, d: T) T

```

Ah, it’s interesting how `num` here is an _implicitly_ `comptime` parameter (so, passing the same T but different `num` will monomrphize, and you can’t select a `num` at runtime). That’s because having a function _body_ as a field makes a type comptime-only:

```zig
    return struct {
        empty: T,
        combine: fn (T, T) T,
    }

```

This is something I haven’t quite realized before, thanks!

---

<div class="post-metadata">

**Author:** ![Calder-Ty](https://ziggit.dev/user_avatar/ziggit.dev/calder-ty/32/10237_2.png) [@Calder-Ty](https://ziggit.dev/u/Calder-Ty)\
**Post date:** [October 23, 2026, 11:08am UTC](https://ziggit.dev/t/we-have-traits-at-home-blog-post/18059/10 "2026-10-23T11:08:43Z")

</div>

Nice writeup and very thorough.

> Traits are a sometimes treat.

That is a very important piece of advice, I wish I had known back when I programmed more in Rust.

---

<div class="post-metadata">

**Author:** ![vincentd](https://ziggit.dev/user_avatar/ziggit.dev/vincentd/32/9870_2.png) [@vincentd](https://ziggit.dev/u/vincentd)\
**Post date:** [October 23, 2026, 12:00pm UTC](https://ziggit.dev/t/we-have-traits-at-home-blog-post/18059/11 "2026-10-23T12:00:15Z")

</div>

I think this is for things like the `"{f}"` and `"{qf}"` format specifiers in `std.Io.Writer`.

The way it works now: you add a `format` method to your type, e.g.

```zig
    const Vec2 = struct {
        const SelfType = @This();
        x: f32,
        y: f32,

        pub fn format(self: SelfType, w: *Writer) Error!void {
            return w.print("({d:.3},{d:.3})", .{ self.x, self.y });
        }
    };

```

Then when you use `std.Io.Writer.print(.{"{f}", .{ val })`, it will be resolved at comptime, eventually leading to the line:

```zig
pub fn printValue(
    w: *Writer,
    comptime fmt: []const u8,
    options: std.fmt.Options,
    value: anytype,
    max_depth: usize,
) Error!void {
    ...
    'f' => return value.format(w),
    ...
}

```

Very handy. I’m interested in what it would look like with “Traits at home”, but not interested enough to do it myself. 😛

* * *

> [@Calder-Ty](#):
>
> > Traits are a sometimes treat.
> 
> That is a very important piece of advice, I wish I had known back when I programmed more in Rust.

This seems to be a pattern - when a language has generic programming features, you are recommended to _not_ use them unless you have to:

- C - macros, void\*.
- C++ - macros, templates, polymorphism.
- Zig - comptime, anytype.

When all you have is a `Hammerable`, you can only hope it’s a nail.

---

<div class="post-metadata">

**Author:** ![mostsquares](https://ziggit.dev/letter_avatar_proxy/v4/letter/m/ea666f/32.png) [@mostsquares](https://ziggit.dev/u/mostsquares)\
**Post date:** [October 23, 2026, 7:20pm UTC](https://ziggit.dev/t/we-have-traits-at-home-blog-post/18059/12 "2026-10-23T19:20:50Z")

</div>

Awesome post!

I had recently tried another form of alternative to compile-time duck typing and VTables… wondering how people would react to it?

My idea was to define an “interface” as a (unused/abstract) struct `Interface` with some fields and methods, and then to offer a comptime `assertImplements(Interface, @TypeOf(someAnytypeArg))` do some compile-time enforcement of duck typing. I am not sure if that’s totally redundant with Zig’s compiler, but it could serve as a form of documentation and point out the missing or incorrect parts of the interface. Of course, in my [implementation of this idea](https://github.com/cwindolf/zit) I found it hard to do a perfect job of that and had to cut some corners, but I was able to handle a lot of cases.

My understanding of traits is sort of limited, so I am not sure about how much this interface idea overlaps with the kinds of problems traits solve, but maybe it is at least enough not to be off-topic in this thread :).

In any case, even coming from Python I was missing some form of safety/documentation/tooling around type annotations of duck-typed arguments, so this post was great to read!! Thank you again and I hope it catches on!
