# Halloween Challenge: Cursed Zig

**URL:** <https://ziggit.dev/t/halloween-challenge-cursed-zig/18153>\
**Category:** Brainstorming\
**Created:** [October 30, 2026, 3:08pm UTC](https://ziggit.dev/t/halloween-challenge-cursed-zig/18153 "2026-10-30T15:08:10Z")\
**Posts on this page:** 2\
**Page:** 2

<div class="post-metadata">

**Author:** ![Fejiberglibstein](https://ziggit.dev/user_avatar/ziggit.dev/fejiberglibstein/32/10378_2.png) [@Fejiberglibstein](https://ziggit.dev/u/Fejiberglibstein)\
**Post date:** [October 31, 2026, 4:12pm UTC](https://ziggit.dev/t/halloween-challenge-cursed-zig/18153/21 "2026-10-31T16:12:16Z")

</div>

I’ve been using this pattern for awhile now, and it’s really nice. I’ve also found value in treating indices as first class values that are just as important as the types they point to.

For example, instead of doing this:

```zig
const Container = struct {
    foos: []const Foo,
    bars: []const Bar,

    const Foo = struct {
        abc: u32,
        def: u32,

        const Index = enum(u32) { _ };
    };

    const Bar = struct {
        ghi: u32,
        jkl: u32,
        const Index = enum(u32) { _ };
    };

    pub fn getFoo(container: *const Container, idx: Foo.Index) Foo {
        return container.foos[@backingInt(idx)];
    }

    pub fn getBar(container: *const Container, idx: Bar.Index) Bar {
        return container.bars[@backingInt(idx)];
    }
};

```

I find that it is better to do this:

```zig
const Container = struct {
    foos: []const Foo,
    bars: []const Bar,

    const Foo = struct {
        abc: u32,
        def: u32,

        const Index = enum(u32) {
            _,

            pub fn get(idx: @This(), container: *const Container) Foo {
                return container.foos[@backingInt(idx)];
            }
        };
    };

    const Bar = struct {
        ghi: u32,
        jkl: u32,

        const Index = enum(u32) {
            _,

            pub fn get(idx: @This(), container: *const Container) Bar {
                return container.bars[@backingInt(idx)];
            }
        };
    };
};

```

This provides the following ergonomics:

- `Container`’s namespace is smaller. Less things to look at and think about when dealing with that type
- `foo_idx.get(container)` is nicer to type than `container.getFoo(foo_idx)`. In terms of (english) grammar, in the first example, `foo_idx` is the subject and `container` is the object of the preposition: “foo\_idx gets Foo from container”. I have found that by treating `foo_idx` as the subject, my code naturally leans toward [using indices instead of pointers](https://www.hytradboi.com/2025/05c72e39-c07e-41bc-ac40-85e8308f2917-programming-without-pointers)
- You can easily add new methods onto the index type that you wouldn’t really be able to do otherwise. For example, if `container` stored a MultiArrayList of `Foo`, then you could have individual getters for each of the properties:

```zig
const Container = struct {
    foos: std.MultiArrayList(Foo),

    const Foo = struct {
        abc: Abc,
        def: Def,
        // Foo is some sort of intrusive linked list
        next: Foo.Index,

        const Index = enum(u32) {
            none = std.math.maxInt(u32),
            _,

            pub fn abc(idx: @This(), container: *const Container) Abc {
                return container.foos.items(.abc)[@backingInt(idx)];
            }
            pub fn def(idx: @This(), container: *const Container) Def {
                return container.foos.items(.def)[@backingInt(idx)];
            }
            pub fn ghi(idx: @This(), container: *const Container) Ghi {
                return container.foos.items(.ghi)[@backingInt(idx)];
            }
            pub fn next(idx: @This(), container: *const Container) Foo.Index {
                return container.foos.items(.next)[@backingInt(idx)];
            }

        };
    };
};

```

This makes using indexes even more ergonomic, since you can do

```zig
pub fn something(container: *Container, foo: Foo.Index) void {
    var curr = foo;
    while (curr != .none) : (curr = curr.next(container)) {
        const abc = curr.abc(container);
        const def = curr.def(container);
        abc.foo(def); // do stuff
    }
}

```

Here are some examples of code I’ve written that uses this approach:

- zirp’s [node](https://codeberg.org/zirp/zirp/src/commit/fe073932ecede087da279f8a3a0f8f6fd3bd25f6/src/Ast.zig#L83) and [token](https://codeberg.org/zirp/zirp/src/commit/fe073932ecede087da279f8a3a0f8f6fd3bd25f6/src/Ast.zig#L906) index types
- [Virtual file system](https://github.com/Fejiberglibstein/polka/blob/main/src/VirtualFilesystem.zig#L123) implementation I wrote, this makes use of the intrusive linked list thing I mentioned. I wrote this code awhile ago, though; If I rewrote it today I would definitely have getters on the `Index` type for all of the fields on `Entry`!

(sorry no cursed code here, but this has been on my mind for awhile and now seemed like a good time to talk about it lol. Maybe I’ll write a blog post about this eventually)

---

<div class="post-metadata">

**Author:** ![zivoy](https://ziggit.dev/user_avatar/ziggit.dev/zivoy/32/4158_2.png) [@zivoy](https://ziggit.dev/u/zivoy)\
**Post date:** [October 31, 2026, 6:53pm UTC](https://ziggit.dev/t/halloween-challenge-cursed-zig/18153/22 "2026-10-31T18:53:14Z")

</div>

That’s why I am iffy on it, it’s really helpful and prevents a lot of bugs.

however, I find that in Go it is much much nicer, since type aliases of a base type don’t implicitly coerce into one another, they only coerce explicitly (or implicitly if it’s a compatible type and you use it to index an array or access a map).  
And you don’t have to do an enum trick just to stop the compiler from treating it all as the base type.

```go
type IndexType uint32
type IndexType2 uint32

func someFunc(idx IndexType) {}

func main() {
	var idx1 IndexType
	var idx2 IndexType2
	var number uint32

	someFunc(idx1) // good
	someFunc(idx2) // cannot use idx2 (variable of uint32 type IndexType2) as IndexType value in argument to someFunc
	someFunc(number) // cannot use number (variable of type uint32) as IndexType value in argument to someFunc
	someFunc(7) // good
	someFunc(IndexType(number)) // good
	someFunc(IndexType(idx2)) // good
}

```

^ same if I made a function take `uint32`, `IndexType` 1&2 wont work on that function unless they are explicitly coerced down to their base type

Though doing it like this in Go I still get the benefit of being able to put functions on the types and in Zig that is not possible if its just a `const IndexType = u32;`, so the enum trick does have some merit in that regard.

[Previous page](https://ziggit.dev/t/halloween-challenge-cursed-zig/18153.md?page=1)
