1
0
Fork
You've already forked zig-feedback
0
No description
2026年07月01日 17:05:36 -07:00
LICENSE Initial commit 2026年02月23日 22:00:15 +01:00
README.md idk something I did awhile ago 2026年07月01日 17:05:36 -07:00

zig-feedback

Assorted and unsorted bits of feedback on the Zig programming language. New entries added to the top.

Tuple

Creating tuples via the struct keyboard I think is worse than if it had its own dedicated keyword and identity from a user perspective. Tuples don't support decls, packed, extern, etc. Nearly all of the fields in builtin.Type.Struct don't apply to tuples. Tuples support inline for loop iteration that structs don't. There's already a seperate @Tuple. The only things they share are being concatenative types. Why not actually seperate the concepts in code with:

constMyTuple=tuple{u64,[]constu8};

I've seen several newbie questions that I think would've not been asked had this concept been more distinct.

//////////////////////////////////// reply to ziglang/zig#32047 wip I independently thought about suggesting this change, so I'm glad to see it proposed here. Skipping over my reasoning that's already in the OP, a few additional supporting arguments:

I have seen at least a couple of times where someone was asking for help, due to confusion about not realizing tuples are a thing that has separate behavior from structs. Unfortunately I don't have a specific example on hand.

I've written comptime code that really wanted to just be dealing with tuples, but

std.debug.print("array: {}\n",.{[1]u32==[1]u32});// truestd.debug.print("tuple: {}\n",.{struct{u32}==struct{u32}});// truestd.debug.print("struct: {}\n",.{struct{a:u32}==struct{a:u32}});// false

////////////////////////////////////

Subslice of tuple

const a = .{1, 2, 3};
const b = a[1..];
try std.testing.expectEqualDeep(b, .{2,3});

They already support ++. This would handle a lot of cases where I end up having to define a tuple type with @Tupple, then immediately fill out one instance once.

TODO

I like the todo proposal (could also be 'wip') https://github.com/ziglang/zig/issues/24328 Specifically a version which surpresses unused variable errors. I almost entirely don't care about unused variable errors, but sometimes I do stub some stuff out.

IO, and Blessed Systems

There is a line which the Zig's main repo draws around a feature set that becomes blessed, and outside of which gets tough luck. The approach thus far has been pragmatic, but it feels like some aspects of the language can be spun out to give greater user control while also releaving pressure on the main repo.

Examples:

  • All of the backends are baked into the compiler, which doesn't actually seem necessary. Why is the compiler not a library that is imported by various backends? Does Spirv need to live in the main repo?
  • Some operations (eg, @min) crowd the builtin space as special ways of accessing lower level backend features. Why are these not simply library functions which have a pure Zig implementation, with an additional hook that the backends override. Or just assembly or hooks for providing backend specific info? The line of "everyone gets it if one piece of hardware wants it special" is bizzare.
  • IO feels like a complicated ball of code, overly specialized and not that extendable. Some systems (eg, apps running in a browser in wasm) will simply have different io primatives, boxing them into posix feels like the wrong move. I think something closer to the IO being aliased to a specific Io impl feels more correct, and let Io instances actually not have a completely standard interface.
  • Basically, a lot of the standard libraries use of checking architecture and OS feels very impure. Impure can be good when it's practical, but it feels like some purity can lead to practical gains here.

Eg: Add @import("backend"), which the backend gets to supply. In that, include the IO instance. The std's IO would reference that IO. If it needs configuration, it can reference @import("root") or by backend specific options in the build system.

Type Context

Replace rls with type context system. Type context has optional ability for unknown length array, and unknown error set. Type context goes from outer scope to inner scope, actual type comes out. Can be anytype. Fixes the syntax requirements related to array initialization, decl literals with catch statements, and chained decl literals.

Type context seems like the ideal way for math to work. Discussed somewhat here: https://ziggit.dev/t/result-type-spreading/13260

An alternative approach: Have type context going in, and the peer's resolved types all in consideration. No type context (anytype) and comptime_ints would be null. Require all non-null types to match, and have the operation be of that type. Add syntax to directly specify the type of the mathmatical operation, and require proper coersion eg:

const a: u16 = 0;
const b: u16 = 1;
const c: u8 = 2;
// Allowed
const _ = a + b;
const _ = a +:u16 c;
const _ = @intCast(a) +:u8 c;
const _: u16 = a + b;
const _: u8 = @intCast(a + b);
const _: u8 = @intCast(a +:u16 c);
const _: u64 = a +:u32 b;
// Errors
const _ = a + c;
const _: u32 = a + b;
const _ = a +:u8 c;

Reasoning: The precise type used in operations is important. This would require being explicit in cases of ambiguity. However it would also remove a lot of @as noise, with concise math operator type syntax.

There is a bit of weirdness possibly resulting from this:

const _: u32 = @intCast(a + b);

This one is a bit weird, given that u16 can normally coerce to u8. It's using @intCast to remove the type context. Odd that you'd want to add in u16 space before moving to u32 space. It could be written as:

const _: u32 = @intCast(a) + @intCast(b); 

which has similar weirdness. The ideal form is probably:

const _ = a +:u32 b;

However that does require restating a type which may be present in the context (fields or variables). Honestly probably fine.

Inverse of @call.

Something I encountered when writing zig-javascript-bridge is the lack of an inverse of @call: A way to take a function signature and a function which takes a single tuple argument, and turns it into a function reference. I'm not quite sure where on the "solves real problems" to "only abused to do stupid shit" spectrum this idea is. This is specificaly useful for wrapping user functions in something that is exported in a way an external system expects. Idk on name, whatever works.

Simple example:

fnfoo(v:struct{u64,[]constu8})void{_=v;// ...}constbar=@inverseCall(fn(u64,[]constu8),foo);pubfnmain()void{bar(10,"Hello World");}

Only slightly abusive, but also I think nice example,

fnanonymizeMethod(comptimef:anytype)AnonymizeMethod(@TypeOf(f)){// TODO: WRITE}fnAnonymizeMethod(T:type)type{constfn_info=switch(@typeInfo(T)){.@"fn"=>|info|info,else=>unreachable;};constparams=[_]conststd.builtin.Type.Fn.Param{.{.is_generic=false,.is_noalias=false,.type=*anyopaque,}}+fn_info.params.*[1..];return@Fn()// UGH, why is typeInfo so different here???}

Why is align a property of fields and variables?

I'm not sure why the language is set up this way, instead of having alignment be a wrapper around a type, like optional. Creates some weird struct wrapping requirements if you have a type which has alignment requirements for unusual reasons, eg for std140.

Thinking about it more, I think I start to see possible problems with that. I'd have to do a deeper dive to feel out if those problems are real or not.

Tabs vs Spaces

Jk, the section is actually "fix it with fmt". There are at least a couple types of errors that I feel running zig fmt before trying to build should successfully fix:

  • Tabs appearing for indentation. Would be trivial to turn this into spaces.
  • Unequal spaces around arithmatic operators.

I'm indifferent on whether these stay compiler errors, as I always format before compiling. It's just that fmt should be fixing my formatting, and it's weird there's these exceptions.

I'd be fine with zig fmt also fixing unused varables / unnecessary discard errors.

MutliArraylist and zero width entries

It'd be nice, if, eg, I'm adding an enum with only one value that multiarraylist still works. Might require a language change to allow slices of zero width data, but that'd be useful for consistency anyways.

I may write more about:

Things I haven't taken time to explain, but have opinions on:

  • Build system being files outputs and run actions. Running a partial run, partial watch and rebuild system.
  • @compileLog like output.
  • Math
  • Compiler settings not based on using a backend, with options for that backend.
  • rename inline to compflow or something like that. It's about sema, not optimization. Arguments that are comptime should maybe be compflow; I think it's easy to make cases either way. Variables also maybe should be compflow. comptime being the keyword to force an expression into running during comptime makes sense still. Probably shouldn't all be one keyword, because comptime for and compflow for seem to be different beasts, except one doesn't exist, hrm.