{"id":20332486,"url":"https://github.com/permutationlock/ztrait","last_synced_at":"2025-04-11T21:32:58.585Z","repository":{"id":198104000,"uuid":"700087536","full_name":"permutationlock/ztrait","owner":"permutationlock","description":"A simple version of Rust style type traits in Zig","archived":false,"fork":false,"pushed_at":"2023-11-25T18:35:47.000Z","size":126,"stargazers_count":25,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-25T17:23:21.301Z","etag":null,"topics":["generics","zig"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/permutationlock.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.MIT","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-10-03T23:18:53.000Z","updated_at":"2025-02-26T17:30:49.000Z","dependencies_parsed_at":"2023-11-25T19:38:38.110Z","dependency_job_id":null,"html_url":"https://github.com/permutationlock/ztrait","commit_stats":null,"previous_names":["permutationlock/zig_type_traits","permutationlock/ztrait"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/permutationlock%2Fztrait","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/permutationlock%2Fztrait/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/permutationlock%2Fztrait/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/permutationlock%2Fztrait/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/permutationlock","download_url":"https://codeload.github.com/permutationlock/ztrait/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248482930,"owners_count":21111402,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["generics","zig"],"created_at":"2024-11-14T20:26:51.907Z","updated_at":"2025-04-11T21:32:58.543Z","avatar_url":"https://github.com/permutationlock.png","language":"Zig","readme":"# Zig Type Traits\n\n*Disclaimer: This was an exploratory project that I no longer believe\nshould be used for practical purposes. Everything I leared in this\nproject was refined into the\n[zimpl library](https://github.com/permutationlock/zimpl)\nwhich I will continue to maintain.*\n\nAn attempt at implementing something along the lines of Rust type traits in Zig.\nUsing this library you can define traits and compile-time verify that types\nimplement them.\n\nI wrote\n[an article](https://musing.permutationlock.com/posts/blog-working_with_anytype.html)\nabout working with `anytype` which reflects on\ndeveloping this library.\n\n### Related Links\n\nBelow are some related projects and Zig proposal threads that I read while\nimplementing the library.\n\n - [Zig Compile-Time-Contracts](https://github.com/yrashk/zig-ctc)\n - Zig issues:\n   [#1268](https://github.com/ziglang/zig/issues/1268),\n   [#1669](https://github.com/ziglang/zig/issues/1669),\n   [#6615](https://github.com/ziglang/zig/issues/6615),\n   [#17198](https://github.com/ziglang/zig/issues/17198)\n\nI don't have any strong position on proposed\nchanges to the Zig language regarding generics, and I respect the Zig team's\nreasoning for keeping the type system simple.\n\n## Basic use\n\nA trait is simply a comptime function taking a type and returning a struct type\ncontaining only declarations.\nEach declaration of the returned struct is a required declaration that a\ntype must have if it implements the trait. \n\nBelow is a trait that requires implementing types to define an integer\nsub-type `Count`, define an `init` function, and define member functions\n`increment` and `read`.\n\n```Zig\npub fn Incrementable(comptime Type: type) type {\n    return struct {\n        // below is the same as `pub const Count = type` except that during\n        // trait verification it requires that '@typeInfo(Type.Count) == .Int'\n        pub const Count = hasTypeId(.Int);\n\n        pub const init = fn () Type;\n        pub const increment = fn (*Type) void;\n        pub const read = fn (*const Type) Type.Count;\n    };\n}\n```\n\nA type that implements the above `Incrementable` trait is provided below.\n\n```Zig\nconst MyCounter = struct {\n    pub const Count = u32;\n\n    count: Count,\n\n    pub fn init() @This() {\n        return .{ .count = 0 };\n    }\n\n    pub fn increment(self: *@This()) void {\n        self.count += 1;\n    }\n    \n    pub fn read(self: *const @This()) Count {\n        return self.count;\n    }\n};\n```\n\nTo require that a generic type parameter implements a given trait you simply\nneed to add a comptime verification line at the start of your function.\n\n```Zig\npub fn countToTen(comptime Counter: type) void {\n    comptime where(Counter, implements(Incrementable));\n\n    var counter = Counter.init();\n    while (counter.read() \u003c 10) {\n        counter.increment();\n    }\n}\n```\n\n**Note:** If we don't specify that trait verification is comptime then\nverification might be evaluated later during compilation. This results in\nregular duck-typing errors rather than trait implementation errors.\n\nIf we define a type that fails to implement the `Incrementable` trait and pass\nit to `countToTen`, then the call to `where` will produce a compile error.\n\n```Zig\nconst MyCounterMissingDecl = struct {\n    pub const Count = u32;\n\n    count: Count,\n\n    pub fn init() @This() {\n        return .{ .count = 0 };\n    }\n \n    pub fn read(self: *const @This()) Count {\n        return self.count;\n    }\n};\n```\n\n```Shell\ntrait.zig:12:13: error: trait 'count.Incrementable(count.MyCounterMissingDecl)' failed: missing decl 'increment'\n```\n\n## Combining traits\n\nMultiple traits can be type checked with a single call.\n\n```Zig\npub fn HasDimensions(comptime _: type) type {\n    return struct {\n        pub const width = comptime_int;\n        pub const height = comptime_int;\n    };\n}\n\npub fn computeAreaAndCount(comptime T: type) void {\n    comptime where(T, implements(.{ Incrementable, HasDimensions }));\n\n    var counter = T.init();\n    while (counter.read() \u003c T.width * T.height) {\n        counter.increment();\n    }\n}\n```\n\n## Constraining sub-types\n\nTraits can require types to declare sub-types\nthat implement traits.\n\n```Zig\npub fn HasIncrementable(comptime _: type) type {\n    return struct {\n        pub const Counter = implements(Incrementable);\n    };\n}\n\n```\n\n```Zig\npub fn useHolderToCountToTen(comptime T: type) void {\n    comptime where(T, implements(HasIncrementable));\n\n    var counter = T.Counter.init();\n    while (counter.read() \u003c 10) {\n        counter.increment();\n    }\n}\n```\n\n```Zig\npub const CounterHolder = struct {\n    pub const Counter = MyCounter;\n};\n\npub const InvalidCounterHolder = struct {\n    pub const Counter = MyCounterMissingDecl;\n};\n```\n\n```Shell\ntrait.zig:12:13: error: trait 'count.HasIncrementable(count.InvalidCounterHolder)' failed: decl 'Counter': trait 'count.Incrementable(count.MyCounterMissingDecl)' failed: missing decl 'increment'\n```\n\n## Declaring that a type implements a trait\n\nAlongside enforcing trait implementation in generic functions, types themselves\ncan declare that they implement a given trait.\n\n```Zig\nconst MyCounter = struct {\n    comptime { where(@This(), implements(Incrementable)); }\n\n    // ...\n};\n```\n\nThen with `testing.refAllDecls` you can run `zig test` to automatically verify\nthat these traits are implemented.\n\n```Zig\ntest {\n    std.testing.refAllDecls(@This);\n}\n```\n\nCredit to \"NewbLuck\" on the Zig Discord for pointing out this nice pattern.\n\n## Pointer types\n\nOften one will want to pass a pointer type to a function taking\n`anytype`, and it turns out that it is still quite simple to do\ntrait checking.\n\nSingle item pointers allow automatic dereferencing in Zig, e.g.\n`ptr.decl` is `ptr.*.decl`, so it makes sense to define that a pointer\ntype `*T` implements a trait if `T` implements the trait.\n\nTo\naccomplish this, types are passed through an `Unwrap` function before\ntrait checking occurs.\n\n```Zig\npub fn Unwrap(comptime Type: type) type {\n    return switch (@typeInfo(Type)) {\n        .Pointer =\u003e PointerChild(Type),\n        else =\u003e Type,\n    };\n}\n```\n\nTherefore the following function will work just fine with pointers to\ntypes that implement `Incrementable`.\n\n```Zig\npub fn countToTen(counter: anytype) void {\n    comptime where(@TypeOf(counter), implements(Incrementable));\n\n    while (counter.read() \u003c 10) {\n        counter.increment();\n    }\n}\n```\n\n## Slice types\n\nFor slice parameters we usually want the caller to be able to pass\nboth `*[_]T` and `[]T`. The library provides the\n`SliceChild` helper function to verify that a type can coerce to a slice\nandt extract its child type.\n\n```Zig\npub fn incrementAll(list: anytype) void {\n    comptime where(SliceChild(@TypeOf(list)), implements(Incrementable));\n\n    for (list) |*counter| {\n        counter.increment();\n    }\n}\n```\n\nThe above function works directly on parameters that can coerce to a slice,\nbut if required we can force coercion to a slice type as shown below.\n\n```Zig\npub fn incrementAll(list: anytype) void {\n    comptime where(SliceChild(@TypeOf(list)), implements(Incrementable));\n    \n    const slice: []SliceChild(@TypeOf(list)) = list;\n    for (slice) |*counter| {\n        counter.increment();\n    }\n}\n```\n\n## Interfaces: restricting access to declarations \n\nUsing `where` and `implements` we can require that types have\ndeclaration satisfying trait requirements. We cannot, however,\nprevent code from using declarations beyond the scope of the checked\ntraits. Thus it is on the developer to keep traits up to date with how\ntypes are actually used.\n\n### Constructing interfaces within a function\n\nThe `interface` function provides a method to formally restrict\ntraits to be both necessary and sufficient requirements for types.\n\nCalling `interface(Type, Trait)` will construct a comptime instance of a\ngenerated struct type that contains a field for each declaration of\n`Type` that has a matching declaration in `Trait`. The\nfields of this interface struct should then be used in place of the\ndeclarations of `Type`.\n\n```Zig\npub fn countToTen(counter: anytype) void {\n    const ifc = interface(@TypeOf(counter), Incrementable);\n\n    while (ifc.read(counter) \u003c 10) {\n        ifc.increment(counter);\n    }\n}\n```\n\nInterface construction performs the same type checking as `where`.\n\n### Flexible interface parameters\n\nThe type returned by `interface(U, T)` is `Interface(U, T)`, a\nstruct type containing one field for each declaration of\n`T` with default value equal to the corresponding declaration of `U`\n(if it exists and has the correct type).\n\nA more flexible way to work with interfaces is to\ntake an `Interface` struct as an explicit `comptime` parameter.\n\n```Zig\npub fn countToTen(counter: anytype, ifc: Interface(@TypeOf(Counter), Incrementable)) void {\n    while (ifc.read(counter) \u003c 10) {\n        ifc.increment(counter);\n    }\n}\n```\n\nThis allows the caller to override declarations, or\nprovide a custom interface for a type that doesn't have the required\ndeclarations.\n\nUnfortunately, this style of interfaces does not allow\ntrait declarations to depend on one another.\nA restricted version of the `Incrementable` interface that will play\nwell with the interface parameter convention is provided below.\n\n```Zig\npub fn Incrementable(comptime Type: type) type {\n    return struct {\n        pub const increment = fn (*Type) void;\n        pub const read = fn (*const Type) usize;\n    };\n}\n```\n\nAn example of what is possible with this convention is shown below.\n\n```Zig\nconst USize = struct {\n    pub fn increment(i: *usize) void {\n        i.* += 1;\n    }\n\n    pub fn read(i: *const usize) usize {\n        return i.*;\n    }\n};\nvar my_count: usize = 0;\ncountToTen(\u0026my_count, .{ .increment = USize.increment, .read = USize.read });\n```\n\n## Extending the library to support other use cases\n\nUsers can define their own helper functions as needed by wrapping\nand expanding the trait module.\n\n```Zig\n// mytrait.zig\n\n// expose all declaraions from the standard trait module\nconst zt = @import(\"ztrait\");\npub usingnamespace zt;\n\n// define your own convenience functions\npub fn BackingInteger(comptime Type: type) type {\n    comptime zt.where(Type, zt.isPackedContainer());\n\n    return switch (@typeInfo(Type)) {\n        inline .Struct, .Union =\u003e |info| info.backing_integer.?,\n        else =\u003e unreachable,\n    };\n}\n```\n\n## Returns syntax: traits in function definitions\n\nSometimes it can be useful to have type signatures directly in function\ndefinitions. Zig currently does not support this, but there is a hacky\nworkaround using the fact that Zig can evaluate a `comptime` function in\nthe return type location.\n\n```Zig\npub fn sumIntSlice(comptime I: type, list: []const  I) Returns(I, .{\n    where(I, hasTypeId(.Int)),\n}) {\n    var count: I = 0;\n    for (list) |elem| {\n        count += elem;\n    }\n    return count;\n}\n```\n\nThe first parameter of `Returns` is the actual return type of the function,\nwhile the second is an unreferenced `anytype` parameter.\n\n```Zig\npub fn Returns(comptime ReturnType: type, comptime _: anytype) type {\n    return ReturnType;\n}\n```\n\n**Warning:** Error messages can be less helpful when using `Returns`\nbecause the compile error occurs while a function signature is being\ngenerated. This can result in the line number of the original call\nnot be reported unless building with `-freference-trace` (and even then\nthe call site may still be obscured in some degenerate cases).\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpermutationlock%2Fztrait","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpermutationlock%2Fztrait","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpermutationlock%2Fztrait/lists"}