ziglang/zig
259
6.0k
Fork
You've already forked zig
771

remove @TypeOf and anytype; introduce |T| syntax #32099

Open
opened 2026年04月27日 21:08:38 +02:00 by andrewrk · 48 comments

@TypeOf is single-handedly responsible for language specification complexity. For example the langref contains this text:

The expressions are evaluated, however they are guaranteed to have no runtime side-effects:

It's not too bad but it's certainly a special case. There is no other builtin or syntax that behaves this way.

This proposal is to remove the builtin entirely, along with introducing syntax that addresses the use cases for @TypeOf.

Additional motivation here is that anytype is awkward because it looks like a type but it's actually syntax. Renaming it to anyval (https://github.com/ziglang/zig/issues/5893) does not address that fundamental incongruity. However, unless @TypeOf is removed, then an additional way to get the inferred type is no good. Better to always rely on @TypeOf than to have two ways to do it.

Variable declaration example:

conststd=@import("std");constexpect=std.testing.expect;test"@TypeOf var decl"{constresult=foo();comptimeassert(@TypeOf(result)==i32);}fnfoo()i32{return1234;}

⬇️

conststd=@import("std");constexpect=std.testing.expect;test"var decl typeof syntax"{constresult:|T|=foo();comptimeassert(T==i32);}fnfoo()i32{return1234;}

Function parameter example:

conststd=@import("std");constexpect=std.testing.expect;test"@TypeOf function parameter"{tryexpect(foo(12)==12);}fnfoo(x:anytype)@TypeOf(x){returnx;}

⬇️

conststd=@import("std");constexpect=std.testing.expect;test"inferred function parameter type"{tryexpect(foo(12)==12);}fnfoo(x:|T|)T{returnx;}

Downside of this would be losing access to peer type resolution as a builtin. For example this function from std.math would need to be rewritten. Keep in mind that peer type resolution works on values, not types because comptime-known values influence the result.

pubfnclamp(val:anytype,lower:anytype,upper:anytype)@TypeOf(val,lower,upper){constT=@TypeOf(val,lower,upper);// ...}

⬇️

pubfnclamp(val:|V|,lower:|L|,upper:|U|)Clamp(.{val,lower,upper}){_=V;_=L;_=U;constT=Clamp(val,lower,upper);// ...}pubfnClamp(values:|V|)type{// implement peer type resolution in userland 🤮}

Plus if #32045 were accepted (which I am not intending to endorse by writing this), this function would be impossible to implement. However, if https://github.com/ziglang/zig/issues/3806 were accepted, then this function would be fairly reasonable to implement without @TypeOf.

`@TypeOf` is single-handedly responsible for language specification complexity. For example the langref contains this text: > The expressions are evaluated, however they are guaranteed to have no *runtime* side-effects: It's not too bad but it's certainly a special case. There is no other builtin or syntax that behaves this way. This proposal is to remove the builtin entirely, along with introducing syntax that addresses the use cases for `@TypeOf`. Additional motivation here is that `anytype` is awkward because it looks like a type but it's actually *syntax*. Renaming it to `anyval` (https://github.com/ziglang/zig/issues/5893) does not address that fundamental incongruity. However, unless `@TypeOf` is removed, then an additional way to get the inferred type is no good. Better to always rely on `@TypeOf` than to have two ways to do it. Variable declaration example: ```zig const std = @import("std"); const expect = std.testing.expect; test "@TypeOf var decl" { const result = foo(); comptime assert(@TypeOf(result) == i32); } fn foo() i32 { return 1234; } ``` ⬇️ ```zig const std = @import("std"); const expect = std.testing.expect; test "var decl typeof syntax" { const result: |T| = foo(); comptime assert(T == i32); } fn foo() i32 { return 1234; } ``` Function parameter example: ```zig const std = @import("std"); const expect = std.testing.expect; test "@TypeOf function parameter" { try expect(foo(12) == 12); } fn foo(x: anytype) @TypeOf(x) { return x; } ``` ⬇️ ```zig const std = @import("std"); const expect = std.testing.expect; test "inferred function parameter type" { try expect(foo(12) == 12); } fn foo(x: |T|) T { return x; } ``` Downside of this would be losing access to peer type resolution as a builtin. For example this function from `std.math` would need to be rewritten. Keep in mind that peer type resolution works on *values, not types* because comptime-known values influence the result. ```zig pub fn clamp(val: anytype, lower: anytype, upper: anytype) @TypeOf(val, lower, upper) { const T = @TypeOf(val, lower, upper); // ... } ``` ⬇️ ```zig pub fn clamp(val: |V|, lower: |L|, upper: |U|) Clamp(.{val, lower, upper}) { _ = V; _ = L; _ = U; const T = Clamp(val, lower, upper); // ... } pub fn Clamp(values: |V|) type { // implement peer type resolution in userland 🤮 } ``` Plus if #32045 were accepted (which I am not intending to endorse by writing this), this function would be impossible to implement. However, if https://github.com/ziglang/zig/issues/3806 were accepted, then this function would be fairly reasonable to implement without `@TypeOf`.
andrewrk added this to the Urgent milestone 2026年04月27日 21:08:38 +02:00
Contributor
Copy link

changing anytype to |T| has me very excited but there's a couple cases where fully removing @TypeOf leaves me weary.


test{@compileLog(@TypeOf(foo()));}fnfoo()noreturn{while(true){}}
$ zig-master test test.zig 
test.zig:10:5: error: found compile log statement
 @compileLog(@TypeOf(foo()));
 ^~~~~~~~~~~~~~~~~~~~~~~~~~~
Compile Log Output:
@as(type, noreturn)

vs

test{constx=foo();@compileLog(@TypeOf(x));}fnfoo()noreturn{while(true){}}
$ zig-master test test.zig 
^C

determining if/when const x: |T| = foo(); activates the side effects of foo() could be tricky


test{constx:u8=4;consty:u32=7;@compileLog(@TypeOf(x,y));}
test.zig:7:5: error: found compile log statement
 @compileLog(@TypeOf(x, y));
 ^~~~~~~~~~~~~~~~~~~~~~~~~~
Compile Log Output:
@as(type, u32)

@TypeOf with multiple arguments is also used to determine the resulting peer type of the arguments. how would that use case be handled if this portion of the proposal were to be accepted?

although this interacts with https://github.com/ziglang/zig/issues/22182

changing `anytype` to `|T|` has me very excited but there's a couple cases where fully removing `@TypeOf` leaves me weary. ---- ```zig test { @compileLog(@TypeOf(foo())); } fn foo() noreturn { while (true) {} } ``` ``` $ zig-master test test.zig test.zig:10:5: error: found compile log statement @compileLog(@TypeOf(foo())); ^~~~~~~~~~~~~~~~~~~~~~~~~~~ Compile Log Output: @as(type, noreturn) ``` vs ```zig test { const x = foo(); @compileLog(@TypeOf(x)); } fn foo() noreturn { while (true) {} } ``` ``` $ zig-master test test.zig ^C ``` determining if/when `const x: |T| = foo();` activates the side effects of `foo()` could be tricky ---- ```zig test { const x: u8 = 4; const y: u32 = 7; @compileLog(@TypeOf(x, y)); } ``` ``` test.zig:7:5: error: found compile log statement @compileLog(@TypeOf(x, y)); ^~~~~~~~~~~~~~~~~~~~~~~~~~ Compile Log Output: @as(type, u32) ``` `@TypeOf` with multiple arguments is also used to determine the resulting peer type of the arguments. how would that use case be handled if this portion of the proposal were to be accepted? although this interacts with https://github.com/ziglang/zig/issues/22182
Author
Owner
Copy link

determining if/when const x: |T| = foo(); activates the side effects of foo() could be tricky

You have it precisely backwards. @TypeOf makes it more subtle which subset of side effects happen from the expression inside. Meanwhile the |T| syntax removes that concept from the language entirely. There is never a question of what side effects occur, the expression is not special.

> determining if/when `const x: |T| = foo();` activates the side effects of `foo()` could be tricky You have it precisely backwards. `@TypeOf` makes it more subtle which subset of side effects happen from the expression inside. Meanwhile the `|T|` syntax removes that concept from the language entirely. There is never a question of what side effects occur, the expression is not special.

Im woundering about the issue with peer type resolution:

pubfnclamp(val:|V|,lower:|L|,upper:|U|)Clamp(.{val,lower,upper}){

Im assuming |T| decares T as an identifier, which can later be referenced. Why did you rule out the possiblity of
making |T| be able to form a peer-type-resolution-unit (or whatever you want to call it) with the other |T| that where used before? I can imagine that to be a difficult to implement and may also not play nicely with incremental, but it would be at least worth considering/talking about, no?

Im woundering about the issue with peer type resolution: ```zig pub fn clamp(val: |V|, lower: |L|, upper: |U|) Clamp(.{val, lower, upper}) { ``` Im assuming `|T|` decares `T` as an identifier, which can later be referenced. Why did you rule out the possiblity of making `|T|` be able to form a peer-type-resolution-unit (or whatever you want to call it) with the other `|T|` that where used before? I can imagine that to be a difficult to implement and may also not play nicely with incremental, but it would be at least worth considering/talking about, no?
Author
Owner
Copy link

Why did you rule out the possiblity of
making |T| be able to form a peer-type-resolution-unit (or whatever you want to call it) with the other |T| that where used before?

read carefully, I answered exactly this question

> Why did you rule out the possiblity of > making `|T|` be able to form a peer-type-resolution-unit (or whatever you want to call it) with the other `|T|` that where used before? read carefully, I answered exactly this question

Since all captures are const,

varFoo=@TypeOf(foo);

would have to become

constunused:|Foo|=foo;varFooMut=Foo;

However, since this pattern seems to be extremely uncommon in practice, I don't think this is actually an issue.

There is also the question of "what if you want to get the type of something without actually using the value?"

  • If the value is a parameter, in which case you just discard the value with _: |Foo|
  • If the value is the result of a function call or some other expression, though, you would have to do const unused: |Foo| = bar().qux. This is arguably worse than status quo const Foo = @TypeOf(bar().qux).

However, I don't think either of the above cases are common enough to be a problem.

One more use case:
People sometimes use @TypeOf in a function return type to calculate a return type based on calling some other function with the same arguments. That is, fn foo(a: anytype, b: anytype) @TypeOf(bar(a, b)). This is actually decently common.

However, if I may be so bold as to speculate, your answer will probably be "you shouldn't be using such metaprogramming facilities anyways".

Since all captures are const, ```zig var Foo = @TypeOf(foo); ``` would have to become ```zig const unused: |Foo| = foo; var FooMut = Foo; ``` However, since this pattern seems to be extremely [uncommon in practice](https://sourcegraph.com/search?q=lang:Zig+/var+%5Ba-zA-Z_%5D%2B+%3D+%40TypeOf/&patternType=regexp&sm=0), I don't think this is actually an issue. There is also the question of "what if you want to get the type of something without actually using the value?" - If the value is a parameter, in which case you just discard the value with `_: |Foo|` - If the value is the result of a function call or some other expression, though, you would have to do `const unused: |Foo| = bar().qux`. This is arguably worse than status quo `const Foo = @TypeOf(bar().qux)`. However, I don't think either of the above cases are common enough to be a problem. One more use case: People sometimes use @TypeOf in a function return type to calculate a return type based on calling some other function with the same arguments. That is, `fn foo(a: anytype, b: anytype) @TypeOf(bar(a, b))`. [This is actually decently common. ](https://sourcegraph.com/search?q=lang:Zig+/%5C%29+%40TypeOf%5C%28%5Ba-zA-Z_%5D%2B%5C%28/&patternType=regexp&sm=0) However, if I may be so bold as to speculate, your answer will probably be "you shouldn't be using such metaprogramming facilities anyways".
Author
Owner
Copy link

It is indeed very bold of you to put words in my mouth. I prefer to speak for myself.

It is indeed very bold of you to put words in my mouth. I prefer to speak for myself.

How would this work with globals?

Example from a real life project:

pub const redirectMain = if (buildGen.testingOtherMain) buildGen.redirectMain
 else @import("defaultMain.zig").main;
pub fn main() @typeInfo(@TypeOf(redirectMain)).@"fn".return_type.? {
 initGlobals();
 return redirectMain();
}

Possible workaround?


pub fn main() @typeInfo(struct { pub const tmp: |T| = redirectMain; }.T).@"fn".return_type.? {...}

Edit: Possible userland implementation of @TypeOf would probably be better for that use case:

fn typeOf(_: |T|) type {
 return T;
}

This does however have the downside that it wouldn't work with var, the program below doesn't compile for example:

var x: u32 = undefined;
fn userLand(tmp: anytype) type {
 return @TypeOf(tmp);
}
pub fn main() void {
 // Not ok 
 std.debug.print("{}\n", .{userLand(x)});
 //ok
 std.debug.print("{}\n", .{@TypeOf(x)});
}
How would this work with globals? Example from a real life project: ``` pub const redirectMain = if (buildGen.testingOtherMain) buildGen.redirectMain else @import("defaultMain.zig").main; pub fn main() @typeInfo(@TypeOf(redirectMain)).@"fn".return_type.? { initGlobals(); return redirectMain(); } ``` Possible workaround? ``` pub fn main() @typeInfo(struct { pub const tmp: |T| = redirectMain; }.T).@"fn".return_type.? {...} ``` Edit: Possible userland implementation of @TypeOf would probably be better for that use case: ``` fn typeOf(_: |T|) type { return T; } ``` This does however have the downside that it wouldn't work with var, the program below doesn't compile for example: ``` var x: u32 = undefined; fn userLand(tmp: anytype) type { return @TypeOf(tmp); } pub fn main() void { // Not ok std.debug.print("{}\n", .{userLand(x)}); //ok std.debug.print("{}\n", .{@TypeOf(x)}); } ```

@Plebosaur wrote in #32099 (comment):

How would this work with globals?

Example from a real life project:

pub const redirectMain = if (buildGen.testingOtherMain) buildGen.redirectMain
 else @import("defaultMain.zig").main;
pub fn main() @typeInfo(@TypeOf(redirectMain)).@"fn".return_type.? {
 initGlobals();
 return redirectMain();
}

By using arcane block hacks, of course:

pub const redirectMain =
 if (buildGen.testingOtherMain)
 buildGen.redirectMain
 else
 @import("defaultMain.zig").main
;
pub const RedirectMain: type = blk: {
 _: |T| = redirectMain;
 break :blk T;
};
pub fn main() @typeInfo(RedirectMain).@"fn".return_type.? {
 initGlobals();
 return redirectMain();
}
@Plebosaur wrote in https://codeberg.org/ziglang/zig/issues/32099#issuecomment-13908983: > How would this work with globals? > > Example from a real life project: > > ```text > pub const redirectMain = if (buildGen.testingOtherMain) buildGen.redirectMain > else @import("defaultMain.zig").main; > > pub fn main() @typeInfo(@TypeOf(redirectMain)).@"fn".return_type.? { > initGlobals(); > return redirectMain(); > } > ``` By using arcane block hacks, of course: ``` pub const redirectMain = if (buildGen.testingOtherMain) buildGen.redirectMain else @import("defaultMain.zig").main ; pub const RedirectMain: type = blk: { _: |T| = redirectMain; break :blk T; }; pub fn main() @typeInfo(RedirectMain).@"fn".return_type.? { initGlobals(); return redirectMain(); } ```

I like this proposal, but I'd like to suggest a slight tweak to it, that can keep the ergonomics of @TypeOf without exposing arbitrary user-invokable peer type resolution.

If i understand correctly, the issue is that @TypeOf(expr) is kind of a special case that allows users to explicitly invoke peer type resolution of any expression, anywhere anytime.

the proposed alternative to implement it in userland where everyone has to basically implement a mini peer type resolution. I don't like this because this will be both annoying and probably inconsistent.

pubfnclamp(val:|V|,lower:|L|,upper:|U|)Clamp(.{val,lower,upper}){...}

Now what I have in mind is to get rid off @TypeOf() with |R|

Basically this :

pubfnclamp(val:|V|,lower:|L|,upper:|U|)Clamp(.{val,lower,upper}){...}

Would become :

pubfnclamp(val:|V|,lower:|L|,upper:|U|)|R|{...}

Here, |R| would represent the capture type of the evaluation of the function body return type. The compiler determines R from the type checking/resolution of all the return expressions of the function body using the existing type system (including any coercion or peer resolution rules), but without exposing that mechanism directly to the user, instead if all return types can coerce to a valid zig return type, than all good.

it would also work outside of function so :

constresult=foo();trystd.testing.expect(@TypeOf(result)==i32);

would become :

constresult:|T|=foo();trystd.testing.expect(T==i32);

So basically you can't randomly ask the compiler to resolve an expression, but you can still use comptime reflection to do so.

I like this proposal, but I'd like to suggest a slight tweak to it, that can keep the ergonomics of `@TypeOf` without exposing arbitrary user-invokable peer type resolution. If i understand correctly, the issue is that `@TypeOf(expr)` is kind of a special case that allows users to explicitly invoke peer type resolution of any expression, anywhere anytime. the proposed alternative to implement it in userland where everyone has to basically implement a mini peer type resolution. I don't like this because this will be both annoying and probably inconsistent. ```Zig pub fn clamp(val: |V|, lower: |L|, upper: |U|) Clamp(.{val, lower, upper}) { ... } ``` Now what I have in mind is to get rid off `@TypeOf()` with `|R|` Basically this : ```Zig pub fn clamp(val: |V|, lower: |L|, upper: |U|) Clamp(.{val, lower, upper}) { ... } ``` Would become : ```Zig pub fn clamp(val: |V|, lower: |L|, upper: |U|) |R| { ... } ``` Here, `|R|` would represent the capture type of the evaluation of the function body return type. The compiler determines R from the type checking/resolution of all the return expressions of the function body using the existing type system (including any coercion or peer resolution rules), but without exposing that mechanism directly to the user, instead if all return types can coerce to a valid zig return type, than all good. it would also work outside of function so : ```Zig const result = foo(); try std.testing.expect(@TypeOf(result) == i32); ``` would become : ```Zig const result: |T| = foo(); try std.testing.expect(T == i32); ``` So basically you can't randomly ask the compiler to resolve an expression, but you can still use comptime reflection to do so.
Contributor
Copy link

in this new paradigm, is there a valid syntax for using the equivalent to old anytype without giving the type a name?
something like

fnfoo(x:|_|)void{...}

maybe?

in this new paradigm, is there a valid syntax for using the equivalent to old anytype without giving the type a name? something like ```zig fn foo(x: |_|) void { ... } ``` maybe?

anytype and TypeOf need to be improved. And the new grammar is not a subset of the old grammar. Rather, it is a superset because I can...
fn TypeOf(value: |T|) type { return T; }

Moreover, this raises a new issue. The type of fn (para1: anytype) @TypeOf(para1) {} is fn (anytype) anytype. And what is type of fn (para1: |T|) T {}?

`anytype` and `TypeOf` need to be improved. And the new grammar is not a subset of the old grammar. Rather, it is a superset because I can... `fn TypeOf(value: |T|) type { return T; }` Moreover, this raises a new issue. The type of `fn (para1: anytype) @TypeOf(para1) {}` is `fn (anytype) anytype`. And what is type of `fn (para1: |T|) T {}`?
Contributor
Copy link

@continue2breakpoint that's an argument in favor of this proposal because it shows the proposed behavior is strictly more flexible than status quo

@continue2breakpoint that's an argument in favor of this proposal because it shows the proposed behavior is strictly more flexible than status quo

In addition to const tmp: |X| = x; _ = tmp;,
I'd appreciate equivalent _: |X| = x; for brevity.

(EDIT: Another mind-bending variant of writing it would be _ = @as(|X|, x);.
... At least I think that would be correct / regular? But probably too esotheric, and another special-case you wouldn't want in the grammar nor language specification, so really just pitching that one for laughs.
On the other hand, arbitrary typing of expressions as x: T to replace @as(T, x) would elegantly allow x: |T| as well. But pretty sure that was rejected already, and we'd need to change current block labeling syntax to avoid ambiguity. Maybe not the worst idea, but probably out of scope for this proposal compared to the current lower-impact form.)

In addition to `const tmp: |X| = x; _ = tmp;`, I'd appreciate equivalent `_: |X| = x;` for brevity. (EDIT: Another mind-bending variant of writing it would be `_ = @as(|X|, x);`. ... At least I think that would be correct / regular? But probably too esotheric, and another special-case you wouldn't want in the grammar nor language specification, so really just pitching that one for laughs. On the other hand, arbitrary typing of expressions as `x: T` to replace `@as(T, x)` would elegantly allow `x: |T|` as well. But pretty sure that was rejected already, and we'd need to change current block labeling syntax to avoid ambiguity. Maybe not the worst idea, but probably out of scope for this proposal compared to the current lower-impact form.)

@pierrelgol

Here, |R| would represent the capture type of the evaluation of the function body return type. The compiler determines R from the type checking/resolution of all the return expressions of the function body using the existing type system (including any coercion or peer resolution rules), but without exposing that mechanism directly to the user, instead if all return types can coerce to a valid zig return type, than all good.

This previously existed with anytype and was removed (I can't find the issue).

I would be more interested in |T| in return type position to get the result type from the caller, mirroring what built-ins can already do. But such a feature is redundant, and would make for less explicit code, so I don't really care about it.

@pierrelgol > Here, |R| would represent the capture type of the evaluation of the function body return type. The compiler determines R from the type checking/resolution of all the return expressions of the function body using the existing type system (including any coercion or peer resolution rules), but without exposing that mechanism directly to the user, instead if all return types can coerce to a valid zig return type, than all good. This previously existed with `anytype` and was removed (I can't find the issue). I would be more interested in `|T|` in return type position to get the *result type from the caller*, mirroring what built-ins can already do. But such a feature is redundant, and would make for less explicit code, so I don't really care about it.
Contributor
Copy link

Note this would be a good time to think about capturing pointers child types.

fn countScalar(haystack: []const |T|, needle: T) usize
Note this would be a good time to think about capturing pointers child types. ``` fn countScalar(haystack: []const |T|, needle: T) usize ```

@Vulpesx wrote in #32099 (comment)

You might be referring to this issues(?). I guess this feature was delayed because it will make dependencies more complex. If you only use it in simple scenarios like const obj: i32 = returnTypeOverload();, it can force you to explicitly state the type.

@Vulpesx wrote in https://codeberg.org/ziglang/zig/issues/32099#issuecomment-13916882 You might be referring to this [issues](https://github.com/ziglang/zig/issues/447)(?). I guess this feature was delayed because it will make dependencies more complex. If you only use it in simple scenarios like `const obj: i32 = returnTypeOverload();`, it can force you to explicitly state the type.

@wzk Do not let it go this way. I would be annoyed by the Dependent Type Theory and derived features such as the Partial Specialization in template. The concept and interface in Golang are more modern approaches.

@wzk Do not let it go this way. I would be annoyed by the Dependent Type Theory and derived features such as the Partial Specialization in template. The concept and interface in Golang are more modern approaches.

I hope we can bring return type inference back into consideration. I think one of the reasons why @TypeOf was impossible/troublesome to be allowed for use with return types was that the return values are unnamed and with |R| as a return it seems like that problem is just gone.

fnmaxInt()|R|{...}consta:i69=maxInt();fnfibonacci(n:usize)|T|{...}constb:u64=fibonacci(10);

Note that this is different from @pierrelgol's idea (probably even colliding) since I'm proposing evaluating the type from the caller and not from the return statements.

I hope we can bring return type inference back into consideration. I think one of the reasons why `@TypeOf` was impossible/troublesome to be allowed for use with return types was that the return values are unnamed and with `|R|` as a return it seems like that problem is just gone. ```zig fn maxInt() |R| { ... } const a: i69 = maxInt(); fn fibonacci(n: usize) |T| { ... } const b: u64 = fibonacci(10); ``` Note that this is different from @pierrelgol's idea (probably even colliding) since I'm proposing evaluating the type from the caller and not from the return statements.
Contributor
Copy link

There is no other builtin or syntax that behaves this way.

I think function declarations behave this way? In terms of its evaluation effects, @TypeOf(expression) can be lowered as

_=&struct{fnf(// Free variables captured here.)void{_=expression;}}.f;

Whether the calling context is comptime or runtime, the expression is analyzed, but no runtime code is emitted. (in runtime-only context, a simpler lowering of if (runtime(false)) _ = expression; suffices).

But, yeah, if we keep @TypeOf, it probably wants to be elevated to syntax, what it does to "evaluation order" of its argument is too much even for a built-in!

>There is no other builtin or syntax that behaves this way. I _think_ function declarations behave this way? In terms of its evaluation effects, `@TypeOf(expression)` can be lowered as ```zig _ = &struct { fn f( // Free variables captured here. ) void { _ = expression; } }.f; ``` Whether the calling context is comptime or runtime, the `expression` is analyzed, but no runtime code is emitted. (in runtime-only context, a simpler lowering of `if (runtime(false)) _ = expression;` suffices). But, yeah, _if_ we keep `@TypeOf`, it probably wants to be elevated to syntax, what it does to "evaluation order" of its argument is too much even for a built-in!

@Vulpesx wrote in #32099 (comment):

@pierrelgol

Here, |R| would represent the capture type of the evaluation of the function body return type. The compiler determines R from the type checking/resolution of all the return expressions of the function body using the existing type system (including any coercion or peer resolution rules), but without exposing that mechanism directly to the user, instead if all return types can coerce to a valid zig return type, than all good.

This previously existed with anytype and was removed (I can't find the issue).

I would be more interested in |T| in return type position to get the result type from the caller, mirroring what built-ins can already do. But such a feature is redundant, and would make for less explicit code, so I don't really care about it.

To be more precise what I'm proposing is basically not for full return type inference, and not based on call site usage, basically what I'm proposing is to keep the @TypeOf() behavior where a type expression is expected, but instead of it being a builtin it just reuses the proposed new anytype syntax and is only availaible in certain places where a type expression is expected.

Because if the issue is that @TypeOf() is broken because it's a special case builtin that allows user to ask for random peer type resolution anywhere, anytime, with the guarantee of no side effect that's obviously complicated.

Here it would imply fundamentally that there's side effects, because in order to get the type resolved, the function would have to be referenced. and evaluated so |R| would be the result of that type resolution, so it removes the complexity around making sure @TypeOf() is side effect free, without removing the ergonomic.

Also in my mind it shouldn't be a crutch for proper typing |R| should never be allowed to evaluate to anyerror!u32 and could only evaluate to the rhs of the union which would be u32 , so in my mind the final result should be fairly restrictive too.

//allowedpubfnfoo(a:|T|)anyerror!|R|{...}//allowedpubfnfoo(a:|T|)!|R|{...}//allowedpubfnfoo(a:|T|)error{A,B,C}!|R|{...}//allowedpubfnfoo(a:|T|)!?|R|{...}//allowedpubfnfoo(a:|T|)?|R|{...}//allowedpubfnfoo(a:|T|)|R|{...}// not allowed |R| can't hide that this would evaluate to -> error{IsFive}!?u32pubfnbar(a:|T|)|R|{if(a==5){returnerror.IsFive;}elseif(a<5){returnnull;}else{returna;}}pubfnmain()!void{consta=trybar(@as(u32,5);_=a;}
@Vulpesx wrote in https://codeberg.org/ziglang/zig/issues/32099#issuecomment-13916882: > @pierrelgol > > > Here, |R| would represent the capture type of the evaluation of the function body return type. The compiler determines R from the type checking/resolution of all the return expressions of the function body using the existing type system (including any coercion or peer resolution rules), but without exposing that mechanism directly to the user, instead if all return types can coerce to a valid zig return type, than all good. > > This previously existed with `anytype` and was removed (I can't find the issue). > > I would be more interested in `|T|` in return type position to get the _result type from the caller_, mirroring what built-ins can already do. But such a feature is redundant, and would make for less explicit code, so I don't really care about it. To be more precise what I'm proposing is basically not for full return type inference, and not based on call site usage, basically what I'm proposing is to keep the `@TypeOf()` behavior where a type expression is expected, but instead of it being a builtin it just reuses the proposed new anytype syntax and is only availaible in certain places where a type expression is expected. Because if the issue is that `@TypeOf()` is broken because it's a special case builtin that allows user to ask for random peer type resolution anywhere, anytime, with the guarantee of no side effect that's obviously complicated. Here it would imply fundamentally that there's side effects, because in order to get the type resolved, the function would have to be referenced. and evaluated so `|R|` would be the result of that type resolution, so it removes the complexity around making sure `@TypeOf()` is side effect free, without removing the ergonomic. Also in my mind it shouldn't be a crutch for proper typing |R| should never be allowed to evaluate to `anyerror!u32` and could only evaluate to the rhs of the union which would be `u32` , so in my mind the final result should be fairly restrictive too. ```Zig //allowed pub fn foo(a : |T|) anyerror!|R| {...} //allowed pub fn foo(a : |T|) !|R| {...} //allowed pub fn foo(a : |T|) error{A,B,C}!|R| {...} //allowed pub fn foo(a : |T|) !?|R| {...} //allowed pub fn foo(a : |T|) ?|R| {...} //allowed pub fn foo(a : |T|) |R| {...} // not allowed |R| can't hide that this would evaluate to -> error{IsFive}!?u32 pub fn bar(a : |T|) |R| { if (a == 5) { return error.IsFive; }else if (a < 5) { return null; }else { return a; } } pub fn main() !void { const a = try bar(@as(u32,5); _ = a; } ```

Perhaps, to allow for Peer Type Resolution in functions like clamp (without having to resort to messing with return type semantics proposed above), the option to prepend the capture syntax to the parameter list could be added to the original proposal, something like this?

// The clamp case.fnclamp|T|(val:T,lower:T,upper:T)T{// ...}// Multiple PTRed types allowed as well, maybe? Would it be useful?fnfoo|T,U|(a:T,b:T,c:U,d:U)U{// ...}

EDIT 2: To clarify, in the example above, I imagine T would be strictly resolved from the function parameters, val, lower, and upper. All other uses of T (in the function body and/or the return type) would be the result of the parameters' PTR – nothing other than the function parameters would affect what T actually is.

EDIT 3: Just realized one more thing. The matter of fn foo |T| (a: T, ptr: *T) T. Whether this would be acceptable would probably depend on whether PTR depended on values (status quo) or was made to depend purely on types (IIUC, seems to be what @mlugg is suggesting below?). If it's just types, then I imagine it could be feasible to allow.

Then, to keep to the Zen of only a single way of doing things, disallow this prepended syntax when no PTR is taking place (i.e. the type is present only once in the parameter list).

// This is disallowedfnbar|T|(t:T)T{// ...}// Do this insteadfnbar(t:|T|)T{// ...}

EDIT 1: I admit this last part might be fairly confusing... maybe it's not that good of an idea overall.

Perhaps, to allow for Peer Type Resolution in functions like `clamp` (without having to resort to messing with return type semantics proposed above), the option to prepend the capture syntax to the parameter list could be added to the original proposal, something like this? ```zig // The clamp case. fn clamp |T| (val: T, lower: T, upper: T) T { // ... } // Multiple PTRed types allowed as well, maybe? Would it be useful? fn foo |T, U| (a: T, b: T, c: U, d: U) U { // ... } ``` EDIT 2: To clarify, in the example above, I imagine `T` would be strictly resolved from the function parameters, `val`, `lower`, and `upper`. All other uses of `T` (in the function body and/or the return type) would be the result of the parameters' PTR – nothing other than the function parameters would affect what `T` actually is. EDIT 3: Just realized one more thing. The matter of `fn foo |T| (a: T, ptr: *T) T`. Whether this would be acceptable would probably depend on whether PTR depended on values (status quo) or was made to depend purely on types (IIUC, seems to be what @mlugg is suggesting below?). If it's just types, then I imagine it could be feasible to allow. Then, to keep to the Zen of only a single way of doing things, disallow this prepended syntax when no PTR is taking place (i.e. the type is present only once in the parameter list). ```zig // This is disallowed fn bar |T| (t: T) T { // ... } // Do this instead fn bar(t: |T|) T { // ... } ``` EDIT 1: I admit this last part might be fairly confusing... maybe it's not that good of an idea overall.
Owner
Copy link

Keep in mind that peer type resolution works on values, not types because comptime-known values influence the result.

FWIW, this is something I have been wanting to change, because it's a point of complexity in the compiler, potential confusion for users, and causes differences when running code at runtime vs comptime. The only reason I haven't already opened a proposal to change this is that the fate of #3806 would heavily impact it---if we have ranged integers then I believe that proposal would be almost invisible to users (PTR does not rely heavily on the peer values, only in some edge cases).

> Keep in mind that peer type resolution works on values, not types because comptime-known values influence the result. FWIW, this is something I have been wanting to change, because it's a point of complexity in the compiler, potential confusion for users, and causes differences when running code at runtime vs comptime. The only reason I haven't already opened a proposal to change this is that the fate of [#3806](https://github.com/ziglang/zig/issues/3806) would heavily impact it---if we have ranged integers then I believe that proposal would be almost invisible to users (PTR does not rely *heavily* on the peer values, only in some edge cases).

@andrewrk wrote in #32099 (comment):

@TypeOf is single-handedly responsible for language specification complexity. For example the langref contains this text:

The expressions are evaluated, however they are guaranteed to have no runtime side-effects:

It's not too bad but it's certainly a special case. There is no other builtin or syntax that behaves this way.

Why is this considered complex?

And

Additional motivation here is that anytype is awkward because it looks like a type but it's actually syntax. Renaming it to anyval (https://github.com/ziglang/zig/issues/5893) does not address that fundamental incongruity. However, unless @TypeOf is removed, then an additional way to get the inferred type is no good. Better to always rely on @TypeOf than to have two ways to do it.

I think retaining @TypeOf is more appropriate. And limit |T| to only be used in function parameters. For example:

pubfnfoo(a:|T|)T{returna;}

But this change is meaningless when |T| does not support deconstruction. If deconstruction support is provided, callers can obtain clearer parameter descriptions instead of relying on documentation:

pubfnswap(a:*|A|,b:*|B|)void{...}

A typical example is the API of the allocator:

pubfndestroy(self:Allocator,ptr:*|T|)void{...}pubfnfree(self:Allocator,memory:[]|T|)void{...}

This clearly shows that destroy expects pointers and free expects slices, without confusing them.

During migration, anytype can also be directly replaced with |_|.

@andrewrk wrote in https://codeberg.org/ziglang/zig/issues/32099#issue-4721099: > `@TypeOf` is single-handedly responsible for language specification complexity. For example the langref contains this text: > > > The expressions are evaluated, however they are guaranteed to have no _runtime_ side-effects: > > It's not too bad but it's certainly a special case. There is no other builtin or syntax that behaves this way. Why is this considered complex? And > Additional motivation here is that `anytype` is awkward because it looks like a type but it's actually _syntax_. Renaming it to `anyval` (https://github.com/ziglang/zig/issues/5893) does not address that fundamental incongruity. However, unless `@TypeOf` is removed, then an additional way to get the inferred type is no good. Better to always rely on `@TypeOf` than to have two ways to do it. I think retaining `@TypeOf` is more appropriate. And limit `|T|` to only be used in function parameters. For example: ```zig pub fn foo(a: |T|) T { return a; } ``` But this change is meaningless when `|T|` does not support deconstruction. If deconstruction support is provided, callers can obtain clearer parameter descriptions instead of relying on documentation: ```zig pub fn swap(a: *|A|, b: *|B|) void { ... } ``` A typical example is the API of the allocator: ```zig pub fn destroy(self: Allocator, ptr: *|T|) void { ... } pub fn free(self: Allocator, memory: []|T|) void { ... } ``` This clearly shows that `destroy` expects pointers and `free` expects slices, without confusing them. During migration, `anytype` can also be directly replaced with `|_|`.

This is not an answer to the topic, but a side question that popped into my mind (as a non-expert). Why does Zig have both @TypeOf and anytype in the first place? I mean, if they could be replaced by |T|.

This is not an answer to the topic, but a side question that popped into my mind (as a non-expert). Why does Zig have both `@TypeOf` and `anytype` in the first place? I mean, if they could be replaced by `|T|`.

why not create a @PeerType(T, U, ....) builtin? i didnt know @TypeOf allowed PTR, and arguably it's a bad name for peer type resolution operation, (the @TypeOf) should have only had one responsibility in the first place.

(btw i am in favor of this elegant proposal)

why not create a @PeerType(T, U, ....) builtin? i didnt know @TypeOf allowed PTR, and arguably it's a bad name for peer type resolution operation, (the @TypeOf) should have only had one responsibility in the first place. (btw i am in favor of this elegant proposal)

Can the |T| be used in any place where a type can appear? e.g. would fn(list: ArrayList(|T|))... work? Or @Vector(3, |T|)? What about non-type parameters like array and vector lengths?
(I'm pretty sure that this would at least be theoretically possible for simple generics by string matching on @typeName)

e.g. a simple example would be vector dot product (and I'm sure even after glsl vector primitives, the same pattern would still apply to other applications)

pub fn dot(self: anytype, other: @TypeOf(self)) @typeInfo(@TypeOf(self)).vector.child {
	return @reduce(.Add, self*other);
}
↓
pub fn dot(self: @Vector(|len|, |T|), other: @Vector(len, T)) T {
	return @reduce(.Add, self*other);
}
Can the `|T|` be used in any place where a `type` can appear? e.g. would `fn(list: ArrayList(|T|))...` work? Or `@Vector(3, |T|)`? What about non-type parameters like array and vector lengths? (I'm pretty sure that this would at least be theoretically possible for simple generics by string matching on `@typeName`) e.g. a simple example would be vector dot product (and I'm sure even after glsl vector primitives, the same pattern would still apply to other applications) ``` pub fn dot(self: anytype, other: @TypeOf(self)) @typeInfo(@TypeOf(self)).vector.child { return @reduce(.Add, self*other); } ↓ pub fn dot(self: @Vector(|len|, |T|), other: @Vector(len, T)) T { return @reduce(.Add, self*other); } ```

Can the |T| be used in any place where a type can appear? e.g. would fn(list: ArrayList(|T|))... work?

@IntegratedQuantum My impression is that this would balloon this proposal from a new syntax form into arbitrarily complex function inversion analysis.

F.e. given the function `X(_: |A|)`
fnX(_:|A|)type{if(oracle(A)){returnvoid;}else{returnu8;}}

X(a) == u8, they are the same. (The compiler doesn't know and shouldn't care whether a caller has ever called X(...), or got a void / u8 from somewhere else.)
An argument of x: X(|T|) has to accept u8 exactly if X(...) can return u8, and accept void exactly if it can return void.

So for a userland function the compiler would be required to prove whether an argument is within the set of reachable return values to determine whether it should trigger a type mismatch compile error.

We could build a ruleset that allows this _sometimes, f.e. if only identifiable types (struct, union, enum, opaque) are returned, trace back those ids. We could also make the rules work for type-returning builtin functions like @Vector.
But implementing isVector(T) in userland for if (!isVector(T)) @compileError(...); is already quite simple in status-quo, and syntactically this opens up HUGE generality. (@as(i8, @intCast(|x|)) to allow all values that can cast to i8? etc.)

> [Can the |T| be used in any place where a type can appear? e.g. would fn(list: ArrayList(|T|))... work?](https://codeberg.org/ziglang/zig/issues/32099#issuecomment-13950176) @IntegratedQuantum My impression is that this would balloon this proposal from a new syntax form into arbitrarily complex function inversion analysis. <details><summary>F.e. given the function `X(_: |A|)`</summary> ```zig fn X(_: |A|) type { if (oracle(A)) { return void; } else { return u8; } } ``` </details> `X(a) == u8`, they are the same. (The compiler doesn't know and shouldn't care whether a caller has ever called `X(...)`, or got a `void` / `u8` from somewhere else.) An argument of `x: X(|T|)` has to accept `u8` exactly if `X(...)` can return `u8`, and accept `void` exactly if it can return `void`. So for a userland function the compiler would be required to prove whether an argument is within the set of reachable return values to determine whether it should trigger a type mismatch compile error. We could build a ruleset that allows this _sometimes, f.e. if only identifiable types (`struct`, `union`, `enum`, `opaque`) are returned, trace back those ids. We could also make the rules work for type-returning builtin functions like `@Vector`. But implementing `isVector(T)` in userland for `if (!isVector(T)) @compileError(...);` is already quite simple in status-quo, and syntactically this opens up HUGE generality. (`@as(i8, @intCast(|x|))` to allow all values that can cast to `i8`? etc.)
Contributor
Copy link

@IntegratedQuantum Personally I wouldn't want to see something like this (which looks like C++ style type deduction) implemented in Zig. Systems like these always come with a whole lot of rules that one must remember, coupled with a whole lot of limitations because they can only handle very simple cases. comptime + ordinary type reflection is more powerful and makes these things obsolete.

@IntegratedQuantum Personally I wouldn't want to see something like this (which looks like C++ style type deduction) implemented in Zig. Systems like these always come with a whole lot of rules that one must remember, coupled with a whole lot of limitations because they can only handle very simple cases. `comptime` + ordinary type reflection is more powerful and makes these things obsolete.

I don't have a strong preference for @Macol's idea, but this is indeed a reason why I don't quite agree with @pierrelgol's idea, because when I see |R|, my intuition definitely makes me think its usage aligns with @Macol's idea.

Well, to be honest, I tend to favor @Macol's idea, simply because of consistency with built-in functions. When I see the syntax of built-in functions, I'm very confused, because I assume ordinary functions can do the same thing, but ordinary functions cannot. A similar kind of consistency is also why I like this entire syntax, because @TypeOf as a built-in function, with its peculiarity of not evaluating its internal expressions, indeed makes me feel inconsistent.

I don't have a strong preference for @Macol's idea, but this is indeed a reason why I don't quite agree with @pierrelgol's idea, because when I see `|R|`, my intuition definitely makes me think its usage aligns with @Macol's idea. Well, to be honest, I tend to favor @Macol's idea, simply because of consistency with built-in functions. When I see the syntax of built-in functions, I'm very confused, because I assume ordinary functions can do the same thing, but ordinary functions cannot. A similar kind of consistency is also why I like this entire syntax, because `@TypeOf` as a built-in function, with its peculiarity of not evaluating its internal expressions, indeed makes me feel inconsistent.

Can the |T| be used in any place where a type can appear? e.g. would fn(list: ArrayList(|T|))... work? Or @Vector(3, |T|)? What about non-type parameters like array and vector lengths?

@IntegratedQuantum If the type system of the language undergoes no significant changes, doing this would be very difficult. In fact, even now, attempting to infer this in user space is quite challenging.

The only way I can think of to achieve this is to record all the generic functions and their parameters used to construct it in the type metadata, which would make reverse inference possible. Moreover, this could result in some generics that would normally be considered the same being treated as different. E.g., a generic constructed by calling ArrayList via AutoArrayList might be considered different from a generic constructed directly using ArrayList. This would differ from the current type model and would be quite counterintuitive.

Actually, I really want this feature, so I liked it. But I think this is not achievable in implementation.

> Can the |T| be used in any place where a type can appear? e.g. would fn(list: ArrayList(|T|))... work? Or @Vector(3, |T|)? What about non-type parameters like array and vector lengths? @IntegratedQuantum If the type system of the language undergoes no significant changes, doing this would be very difficult. In fact, even now, attempting to infer this in user space is quite challenging. The only way I can think of to achieve this is to record all the generic functions and their parameters used to construct it in the type metadata, which would make reverse inference possible. Moreover, this could result in some generics that would normally be considered the same being treated as different. E.g., a generic constructed by calling `ArrayList` via `AutoArrayList` might be considered different from a generic constructed directly using `ArrayList`. This would differ from the current type model and would be quite counterintuitive. Actually, I really want this feature, so I liked it. But I think this is not achievable in implementation.

I also think the deconstruction of generic functions is a feature worth supporting.

I still hold my opinion that |T| should only be used on function parameters and return value types. Used to replace anytype, which appears to accept any value but is actually not feasible. In most cases, the parameters declared by anytype have additional limitations, but anytype does not reflect this well. This can easily lead to API misuse.

I noticed the description of type names in the document:

If the struct is in the return expression, it gets named after the function it is returning from, with the parameter values serialized.

Moreover, generic functions are stateless compile time functions. This may mean that we can deconstruct generic function types through type names (or other equivalent data). But this seems to introduce the distinction between equality and equivalence of types.

For return type inference (result position semantics), it may lead to another complexity. Built in functions allow passing the result position type to function parameters, enabling nested calls. However, if this feature is introduced, the evaluation order of the parameter type expression and return type expression of the function will be disrupted. For some functions, the return type is evaluated before the parameter type, while for others, it is evaluated in reverse.

However, the results brought by these slight complexities are not bad. I am still looking forward to introducing it.

I also think the deconstruction of generic functions is a feature worth supporting. I still hold my opinion that `|T|` should only be used on function parameters and return value types. Used to replace `anytype`, which appears to accept any value but is actually not feasible. In most cases, the parameters declared by `anytype` have additional limitations, but `anytype` does not reflect this well. This can easily lead to API misuse. I noticed the description of type names in the document: > If the struct is in the return expression, it gets named after the function it is returning from, with the parameter values serialized. Moreover, generic functions are stateless compile time functions. This may mean that we can deconstruct generic function types through type names (or other equivalent data). But this seems to introduce the distinction between **equality** and **equivalence** of types. For return type inference (result position semantics), it may lead to another complexity. Built in functions allow passing the result position type to function parameters, enabling nested calls. However, if this feature is introduced, the evaluation order of the parameter type expression and return type expression of the function will be disrupted. For some functions, the return type is evaluated before the parameter type, while for others, it is evaluated in reverse. However, the results brought by these slight complexities are not bad. I am still looking forward to introducing it.

@npc1054657282 Actually, Zig's type system can already do this(You even just need use print()). What disappoints me instead is that it’s often impossible to determine whether different parameters might return the same type. Current Zig compiler determine whether the return types of a function (with complex logic) are equal, if and only if the parameters are identical.

Determining whether the types are absolutely identical, not by from the same origin, is the real challenge.

@npc1054657282 Actually, Zig's type system can already do this(You even just need use `print()`). What disappoints me instead is that it’s often impossible to determine whether different parameters might return the same type. Current Zig compiler determine whether the return types of a function (with complex logic) are equal, if and only if the parameters are identical. Determining whether the types are absolutely identical, not by from the same origin, is the real challenge.

@continue2breakpoint wrote in #32099 (comment):

@npc1054657282 Actually, Zig's type system can already do this(You even just need use print()). What disappoints me instead is that it’s often impossible to determine whether different parameters might return the same type. Current Zig compiler determine whether the return types of a function (with complex logic) are equal, if and only if the parameters are identical.

Determining whether the types are absolutely identical, not by from the same origin, is the real challenge.

The parameter values are only used to cache the comptime function call, they do not affect the equivalence of the generic types. That is determined by source location and comptime values captured (which may be parameters).

@continue2breakpoint wrote in https://codeberg.org/ziglang/zig/issues/32099#issuecomment-14001104: > @npc1054657282 Actually, Zig's type system can already do this(You even just need use `print()`). What disappoints me instead is that it’s often impossible to determine whether different parameters might return the same type. Current Zig compiler determine whether the return types of a function (with complex logic) are equal, if and only if the parameters are identical. > > Determining whether the types are absolutely identical, not by from the same origin, is the real challenge. The parameter values are only used to cache the comptime function call, they do not affect the equivalence of the generic types. That is determined by source location and comptime values captured (which may be parameters).

I think that the proposal is actually 2 proposals. The first one is about removing anytype and replacing it by type capture |T| and the second one is about removing or replacing @TypeOf.

For the first one I think the type capture would be a useful and nice feature and it prevent many use of @TypeOf.

For the second one, I agree that @TypeOf is more a syntax than a built-in function and it would be nice to have a dedicated syntax to replace it. Nevertheless I don't see type capture to be able to replace it because the semantic of @TypeOf has the unique property of not evaluating it's argument, that is at my knowledge not available else where in the language. For instance, the implementation: fn typeof(_:|T|) type { return T; } must evaluate his argument and cannot prevent side effect of it thus it's not a replacement of @TypeOf. I do not have any good idea of which syntax to use to express "do not actually evaluate, prevent side effect, and give me the type".

I think that the proposal is actually 2 proposals. The first one is about removing `anytype` and replacing it by type capture `|T|` and the second one is about removing or replacing `@TypeOf`. For the first one I think the type capture would be a useful and nice feature and it prevent many use of `@TypeOf`. For the second one, I agree that `@TypeOf` is more a syntax than a built-in function and it would be nice to have a dedicated syntax to replace it. Nevertheless I don't see type capture to be able to replace it because the semantic of `@TypeOf` has the unique property of not evaluating it's argument, that is at my knowledge not available else where in the language. For instance, the implementation: `fn typeof(_:|T|) type { return T; }` must evaluate his argument and cannot prevent side effect of it thus it's not a replacement of `@TypeOf`. I do not have any good idea of which syntax to use to express "do not actually evaluate, prevent side effect, and give me the type".

@pierrelgol wrote in #32099 (comment):

Would become :

pubfnclamp(val:|V|,lower:|L|,upper:|U|)|R|{...}

Here, |R| would represent the capture type of the evaluation of the function body return type. The compiler determines R from the type checking/resolution of all the return expressions of the function body using the existing type system (including any coercion or peer resolution rules), but without exposing that mechanism directly to the user, instead if all return types can coerce to a valid zig return type, than all good.

I'm not sure having the capture of the return type in the function signature is the right place if you're inferring from the return expression. To do that I would expect to capture the type inside the function.

pubfnclamp(val:|V|,lower:|L|,upper:|U|)R{...mostofthework...constreturn_val:|R|=<someexpression>;returnreturn_val;}

I would expect a capture in the function signature for the return type to capture how the function was being called.

pubfnclamp(val:|V|,lower:|L|,upper:|U|)|R|{...}consta=@as(u16,clamp(v,l,u));// R is the type u16constb:u32=clamp(v,l,u);// R is the type u32constc=clamp(v,l,u);// R is ambiguous -- Compilation Error

I don't really have an option on if either should be supported. This is just my interpretation of the syntax.

@pierrelgol wrote in https://codeberg.org/ziglang/zig/issues/32099#issuecomment-13909697: > Would become : > > ```Zig > pub fn clamp(val: |V|, lower: |L|, upper: |U|) |R| { ... } > ``` > > Here, `|R|` would represent the capture type of the evaluation of the function body return type. The compiler determines R from the type checking/resolution of all the return expressions of the function body using the existing type system (including any coercion or peer resolution rules), but without exposing that mechanism directly to the user, instead if all return types can coerce to a valid zig return type, than all good. I'm not sure having the capture of the return type in the function signature is the right place if you're inferring from the `return` expression. To do that I would expect to capture the type inside the function. ```zig pub fn clamp(val: |V|, lower: |L|, upper: |U|) R { ...most of the work... const return_val: |R| = <some expression>; return return_val; } ``` I would expect a capture in the function signature for the return type to capture how the function was being called. ```zig pub fn clamp(val: |V|, lower: |L|, upper: |U|) |R| { ... } const a = @as(u16, clamp(v, l, u)); // R is the type u16 const b: u32 = clamp(v, l, u); // R is the type u32 const c = clamp(v, l, u); // R is ambiguous -- Compilation Error ``` I don't really have an option on if either should be supported. This is just my interpretation of the syntax.

No to me |R| is just a name, ideally something more descriptive.

pubfnfoo(a:|Number1|,b:|Number2|,c:|Number3|)|TypeDeterminedByEvaluatingAllPossibleReturn|{returna+b+c;}
No to me `|R|` is just a name, ideally something more descriptive. ```Zig pub fn foo(a: |Number1|, b: |Number2|, c: |Number3|) |TypeDeterminedByEvaluatingAllPossibleReturn| { return a + b + c; } ```

This proposal feels off, but that's mostly due to the new syntax.

I think it makes sense: |whatever| is a binding syntax which binds a name to an actual value provided by the language.

var foo: |T| = 10;

makes total sense when viewed from that perspective.

it also reminds me of the old fn foo(val: @TypeOf(val)) void proposal which i found appealing.

This new syntax makes sense

This proposal feels off, but that's mostly due to the new syntax. I think it makes sense: `|whatever|` is a binding syntax which binds a name to an actual value provided by the language. ``` var foo: |T| = 10; ``` makes total sense when viewed from that perspective. it also reminds me of the old `fn foo(val: @TypeOf(val)) void` proposal which i found appealing. This new syntax makes sense

Seems like a more carefully-designed and ergonomic proposal than https://github.com/ziglang/zig/issues/9260

How would this "captured type" interacts with result type? e.g. const x: |X| = @intCast(num);

Seems like a more carefully-designed and ergonomic proposal than https://github.com/ziglang/zig/issues/9260 How would this "captured type" interacts with result type? e.g. `const x: |X| = @intCast(num);`

How would this "captured type" interacts with result type? e.g. const x: |X| = @intCast(num);

Im assuming most interactions with result type wouldnt be possible because the compiler has no way of knowing which type to select.

> How would this "captured type" interacts with result type? e.g. const x: |X| = @intCast(num); Im assuming most interactions with result type wouldnt be possible because the compiler has no way of knowing which type to select.

If peer type were to be supported with

fnf(a:|T|,b:|T|)...

we could do the following translation

pubfnfoo(val:anytype,lower:anytype,upper:anytype)@TypeOf(val,lower,upper){

⬇️

fnfoo(val:|T|,lower:|T|,upper:|T|)T{

this coerces the types at the callsite; but if foo still needs V,L,U and args in original type, this can be use instead.

pubfnfoo(val:|V|,lower:|L|,upper:|U|)rpt(.{val,lower,upper}){...}fnrpt(comptimetypes:[]consttype)type{comptime{if(types.len==0)@compileError("Need types");if(types.len==1)returntypes[0];varres=rpt2(@as(types[0],undefined),@as(types[1],undefined));for(types[2..])|t|{res=rpt2(@as(res,undefined),@as(t,undefined));// peer type resolution is associative}returnres;}}fnrpt2(_:|T|,_:|T|)type{returnT;}

a function like resolvePeerTypes could also be added to std.meta instead.


Adding to the idea proposed by @IntegratedQuantum and @rohlem 's reply, it would be possible to support fn(list: ArrayList(|T|)) without doing arbitrary function inversion analysis (which is impossible for non-bijective functions anyways).

The type can store a list of all the functions and theit arguments at it's creation time and match bottom up.
if we do const L = ArrayList(SomeType); the type stores metadata that looks something like []const @Tuple(.{type, []const type}){.{std.ArrayList, .{SomeType}}, .{std.array_list.Aligned, .{SomeType, null}}}

So ArrayList(SomeType) would match both fn(list: array_list.Aligned(|T|, null)) & fn(list: ArrayList(|T|)); but array_list.Aligned(SomeType, null) does not match fn(list: ArrayList(|T|)) which seems like a worthwhile compromise given the users know the limitations.

This does mean that primitives would also need to have such metadata.


This has already been discussed previously

constT=@TypeOf(veryExpensiveFunction(x,y,z));

⬇️

_:|T|=veryExpensiveFunction(x,y,z);

but this does cause side-effects.
But in such cases, either the function's return_type could be use, or if it's null, you should separate out the logic for return type from the function which would be much cleaner.

Having return type inference would be nice too and would deal with most of the use cases of using fn (...) @TypeOf(...). this is already done in labeled blocks like const value = blk: { ... } anyways.

If peer type were to be supported with ```zig fn f (a: |T|, b: |T|) ... ``` we could do the following translation ```zig pub fn foo(val: anytype, lower: anytype, upper: anytype) @TypeOf(val, lower, upper) { ``` ⬇️ ```zig fn foo (val: |T|, lower: |T|, upper: |T|) T { ``` this coerces the types at the callsite; but if foo still needs V,L,U and args in original type, this can be use instead. ```zig pub fn foo(val: |V|, lower: |L|, upper: |U|) rpt(.{val, lower, upper}) { ... } fn rpt(comptime types: []const type) type { comptime { if (types.len == 0) @compileError("Need types"); if (types.len == 1) return types[0]; var res = rpt2(@as(types[0], undefined), @as(types[1], undefined)); for (types[2..]) |t| { res = rpt2(@as(res, undefined), @as(t, undefined)); // peer type resolution is associative } return res; } } fn rpt2(_: |T|, _: |T|) type { return T; } ``` a function like resolvePeerTypes could also be added to std.meta instead. --- Adding to the idea proposed by @IntegratedQuantum and @rohlem 's reply, it would be possible to support `fn(list: ArrayList(|T|))` without doing arbitrary function inversion analysis (which is impossible for non-bijective functions anyways). The type can store a list of all the functions and theit arguments at it's creation time and match bottom up. if we do `const L = ArrayList(SomeType);` the type stores metadata that looks something like `[]const @Tuple(.{type, []const type}){.{std.ArrayList, .{SomeType}}, .{std.array_list.Aligned, .{SomeType, null}}}` So `ArrayList(SomeType)` would match both `fn(list: array_list.Aligned(|T|, null))` & `fn(list: ArrayList(|T|))`; but `array_list.Aligned(SomeType, null)` does not match `fn(list: ArrayList(|T|))` which seems like a worthwhile compromise given the users know the limitations. This does mean that primitives would also need to have such metadata. --- This has already been discussed previously ```zig const T = @TypeOf(veryExpensiveFunction(x, y, z)); ``` ⬇️ ```zig _: |T| = veryExpensiveFunction(x, y, z); ``` but this does cause side-effects. But in such cases, either the function's return_type could be use, or if it's null, you should separate out the logic for return type from the function which would be much cleaner. Having return type inference would be nice too and would deal with most of the use cases of using `fn (...) @TypeOf(...)`. this is already done in labeled blocks like `const value = blk: { ... }` anyways.

I think the proposed syntax is too subtle. Since it is the same as the variable capture syntax it is also potentially confusing.

Why can't the meaning of anytype be changed so it defines "capturing of a type" as a name..

fnf(a:anytypeT,b:T)T
fnf(a:anytypeA,b:anytypeB)resolve(A,B)
constresult:anytypeT=foo();comptimeassert(T==i32);
I think the proposed syntax is too subtle. Since it is the same as the variable capture syntax it is also potentially confusing. Why can't the meaning of anytype be changed so it defines "capturing of a type" as a name.. ```zig fn f (a: anytype T, b: T) T ``` ```zig fn f (a: anytype A, b: anytype B) resolve(A, B) ``` ```zig const result: anytype T = foo(); comptime assert(T == i32); ```

Perhaps *|T| proposal (with pointer destructuring) may also quietly reduce the lingering "trait" discussion by making constraints for the common case simply more ergonomic to write:

// beforefnforeach(iter:anytype,callback:fn(@typeInfo(@TypeOf(iter)).pointer.child.Item)void)void{constT=@typeInfo(@TypeOf(iter)).pointer.child;constItem:type=T.Item;constnext:fn(*T)?Item=T.next;while(next(iter))|item|callback(item);}// after (with pointer destructuring)fnforeach(iter:*|T|,callback:fn(T.Item)void)void{constItem:type=T.Item;constnext:fn(*T)?Item=T.next;while(next(iter))|item|callback(item);}// nice if zig fmt allows '_ = next;' on the same row !fnforeach(iter:*|T|,callback:fn(T.Item)void)void{constItem:type=T.Item;constnext:fn(*T)?Item=T.next;_=next;while(iter.next())|item|callback(item);}
Perhaps `*|T|` proposal (with pointer destructuring) may also quietly reduce the lingering "trait" discussion by making constraints for the common case simply more ergonomic to write: ```zig // before fn foreach(iter: anytype, callback: fn (@typeInfo(@TypeOf(iter)).pointer.child.Item) void) void { const T = @typeInfo(@TypeOf(iter)).pointer.child; const Item: type = T.Item; const next: fn (*T) ?Item = T.next; while (next(iter)) |item| callback(item); } // after (with pointer destructuring) fn foreach(iter: *|T|, callback: fn (T.Item) void) void { const Item: type = T.Item; const next: fn (*T) ?Item = T.next; while (next(iter)) |item| callback(item); } // nice if zig fmt allows '_ = next;' on the same row ! fn foreach(iter: *|T|, callback: fn (T.Item) void) void { const Item: type = T.Item; const next: fn (*T) ?Item = T.next; _ = next; while (iter.next()) |item| callback(item); } ```

As a relatively new user of the language with no knowledge of its implementation, I felt that the syntax was a bit unintuitive. Seeing captures like |T| in variable declarations and function declarations doesn't immediately reveal their purpose. Is there an implementation concern driving the usage of non-alphanumeric characters rather than a keyword?

For example

conststd=@import("std");constexpect=std.testing.expect;test"var decl typeof syntax"{constresult:inferT=foo();comptimeassert(T==i32);}fnfoo()i32{return1234;}

and

conststd=@import("std");constexpect=std.testing.expect;test"inferred function parameter type"{tryexpect(foo(12)==12);}fnfoo(x:inferT)T{returnx;}

In my mind, this would be functionally identical to the original proposal — just, in my opinion, a nicer syntax because now we have special keyword that appears when we are declaring both a value variable and a type variable in the same expression. This is a pretty "cognitively heavy" operation for the person reading the code, so giving it its own special keyword syntax seems appropriate rather than adding more responsibilities to the |T| capture syntax.

Again, to reiterate, I'm ignorant of the underlying implementation complexities -- this is just my 2c of Beginner's Mind perspective.

As a relatively new user of the language with no knowledge of its implementation, I *felt* that the syntax was a bit unintuitive. Seeing captures like `|T|` in variable declarations and function declarations doesn't immediately reveal their purpose. Is there an implementation concern driving the usage of non-alphanumeric characters rather than a keyword? For example ```zig const std = @import("std"); const expect = std.testing.expect; test "var decl typeof syntax" { const result: infer T = foo(); comptime assert(T == i32); } fn foo() i32 { return 1234; } ``` and ```zig const std = @import("std"); const expect = std.testing.expect; test "inferred function parameter type" { try expect(foo(12) == 12); } fn foo(x: infer T) T { return x; } ``` In my mind, this would be functionally identical to the original proposal — just, in my opinion, a nicer syntax because now we have special keyword that appears when we are declaring both a value variable and a type variable in the same expression. This is a pretty "cognitively heavy" operation for the person reading the code, so giving it its own special keyword syntax seems appropriate rather than adding more responsibilities to the `|T|` capture syntax. Again, to reiterate, I'm ignorant of the underlying implementation complexities -- this is just my 2c of Beginner's Mind perspective.

Shouldn't we signal first that said function is generic? Because I'm afraid it could get a nightmare to debug of search in code for (even with regex expressions), catching if/for capture cases instead of what we need. Otherwise it feels to me like these types are appearing out of nowhere:

@generic(A,B,C)// it can be called differentlyfnclamp(x:|A|,y:|B|,z:|C|){// function code}
Shouldn't we signal first that said function is generic? Because I'm afraid it could get a nightmare to debug of search in code for (even with regex expressions), catching if/for capture cases instead of what we need. Otherwise it feels to me like these types are appearing out of nowhere: ```zig @generic(A, B, C) // it can be called differently fn clamp(x: |A|, y: |B|, z: |C|) { // function code } ```

Can we drop the bars? |T| -> T.

// 1. Get a handle of a typeconsta:u23=1;assert(@TypeOf(a)==u23);constA=u23;consta:A=1;assert(A==u23);// 2. Get a handle of a type of a fn paramfnfoo(a:anytype)void{assert(@TypeOf(a)==u23);}fnfoo(a:A)void{assert(A==u23);}// 3. Get info about a typefnfoo(a:anytype)void{constinfo=@typeInfo(@TypeOf(a));// .{ .int = .{ .signedness = .unsigned, .bits = 23 } })}fnfoo(a:A)void{constinfo=@typeInfo(A);// .{ .int = .{ .signedness = .unsigned, .bits = 23 } })}// 4. Construct a type// unchangedconstA=@Int(.unsigned,23);// 5. Specify relations between param type and return type// not work due to @nullable not exists, also does not express arr is a array with length of len.fnfoo(comptimelen:usize,arr:anytype)@nullable(@typeInfo(@TypeOf(arr)).array.child){if(len>0){returnarr[0];}else{returnnull;}}fnfoo(comptimelen:usize,arr:[len]T)?T{if(len>0){returnarr[0];}else{returnnull;}}// 6. Specify relations between params types.pubfngeneral(comptimelen:usize,func:*constfn([len]u8,context:*anyopaque)void,context:*anyopaque)!u64{}pubfngeneral(comptimelen:usize,func:*constfn([len]u8,context:*C)void,context:*C)!u64{}
Can we drop the bars? `|T|` -> `T`. ```zig // 1. Get a handle of a type const a: u23 = 1; assert(@TypeOf(a) == u23); const A = u23; const a: A = 1; assert(A == u23); // 2. Get a handle of a type of a fn param fn foo(a: anytype) void { assert(@TypeOf(a) == u23); } fn foo(a: A) void { assert(A == u23); } // 3. Get info about a type fn foo(a: anytype) void { const info = @typeInfo(@TypeOf(a)); // .{ .int = .{ .signedness = .unsigned, .bits = 23 } }) } fn foo(a: A) void { const info = @typeInfo(A); // .{ .int = .{ .signedness = .unsigned, .bits = 23 } }) } // 4. Construct a type // unchanged const A = @Int(.unsigned, 23); // 5. Specify relations between param type and return type // not work due to @nullable not exists, also does not express arr is a array with length of len. fn foo(comptime len: usize, arr: anytype) @nullable(@typeInfo(@TypeOf(arr)).array.child) { if (len > 0) { return arr[0]; } else { return null; } } fn foo(comptime len: usize, arr: [len]T) ?T { if (len > 0) { return arr[0]; } else { return null; } } // 6. Specify relations between params types. pub fn general(comptime len: usize, func: *const fn ([len]u8, context: *anyopaque) void, context: *anyopaque) !u64 {} pub fn general(comptime len: usize, func: *const fn ([len]u8, context: *C) void, context: *C) !u64 {} ```

I still have some doubts about using the |Captured| syntax to achieve this functionality.

In other scenarios, the scope of captured is limited to the next expression following |captured|. If this expression is a block, then the scope is the entire block.

However, the capture usage within the current type annotation is not like this; its scope is the same as the annotated symbol.

Of course, this is unlikely to cause ambiguity, because the capture in type annotations can be regarded as a special syntax, and there is no need to enforce consistency with other capture syntaxes.
(This is similar to the : before a code block; we all know that this is a code block label, not a type annotation symbol)

However, if we acknowledge that the captures in type annotations are fundamentally different from captures elsewhere, should we consider other forms of expression to avoid confusing beginners?

I still have some doubts about using the `|Captured|` syntax to achieve this functionality. In other scenarios, the scope of `captured` is limited to the next expression following `|captured|`. If this expression is a block, then the scope is the entire block. However, the capture usage within the current type annotation is not like this; its scope is the same as the annotated symbol. Of course, this is unlikely to cause ambiguity, because the capture in type annotations can be regarded as a special syntax, and there is no need to enforce consistency with other capture syntaxes. (This is similar to the `:` before a code block; we all know that this is a code block label, not a type annotation symbol) However, if we acknowledge that the captures in type annotations are fundamentally different from captures elsewhere, should we consider other forms of expression to avoid confusing beginners?

In other scenarios, the scope of captured is limited to the next expression following |captured|. If this expression is a block, then the scope is the entire block.

However, the capture usage within the current type annotation is not like this; its scope is the same as the annotated symbol.

@npc1054657282 I'm personally not bothered by T and x in const x: |T| = ...; both leaking/adding to the enclosing scope.
However, to reuse existing syntax would lead us to something like const x: const T = ...;, which I don't dislike either.
(EDIT: If we instead wanted a new keyword, maybe typed as in const x: typed T = ...;? Or oftype? Which I now realize was suggested above as infer.)

Only function signatures become a little asymmetric then, as fn foo(a: const A) void {...}.
Maybe that's not too bad.
(Or we re-add const before the argument to get fn foo(const a: const A) void {...}? It would be most regular I suppose.)

> [In other scenarios, the scope of `captured` is limited to the next expression following `|captured|`. If this expression is a block, then the scope is the entire block.](https://codeberg.org/ziglang/zig/issues/32099#issuecomment-16080422) > > [However, the capture usage within the current type annotation is not like this; its scope is the same as the annotated symbol.](https://codeberg.org/ziglang/zig/issues/32099#issuecomment-16080422) @npc1054657282 I'm personally not bothered by `T` and `x` in `const x: |T| = ...;` both leaking/adding to the enclosing scope. However, to reuse existing syntax would lead us to something like `const x: const T = ...;`, which I don't dislike either. (EDIT: If we instead wanted a new keyword, maybe `typed` as in `const x: typed T = ...;`? Or `oftype`? [Which I now realize *was* suggested above as `infer`.](https://codeberg.org/ziglang/zig/issues/32099#issuecomment-15891068)) Only function signatures become a little asymmetric then, as `fn foo(a: const A) void {...}`. Maybe that's not too bad. (Or we re-add `const` before the argument to get `fn foo(const a: const A) void {...}`? It *would* be most regular I suppose.)

Can the |T| be used in any place where a type can appear? e.g. would fn(list: ArrayList(|T|))... work?

The only way I can think of to achieve this is to record all the generic functions and their parameters used to construct it in the type metadata, which would make reverse inference possible.

To me, this looks like a good solution. Comptime function calls are already memoized by the Zig compiler, so there is already a mapping from ArrayList(i32) -> struct in the compiler's state, and AFAIK this mapping constitutes the identity of the type: it's what ensures that two calls to ArrayList(i32) resolve to the same type. Therefore, to resolve the type T in ArrayList(|T|) at call sites, we just need to do a reverse lookup on the ArrayList identity mapping. This seems very doable to me. For example, if the identity mapping is a hashmap then we could just maintain an inverse hashmap alongside it.

TL;DR: from what I can tell at a glance, ArrayList(|T|) and even @Vector(|len|, i32) looks very doable with minimal compiler complexity. But I will leave it to the Zig maintainers to determine whether this is actually the case.

> Can the |T| be used in any place where a type can appear? e.g. would fn(list: ArrayList(|T|))... work? > The only way I can think of to achieve this is to record all the generic functions and their parameters used to construct it in the type metadata, which would make reverse inference possible. To me, this looks like a good solution. Comptime function calls are *already* memoized by the Zig compiler, so there is already a mapping from `ArrayList(i32) -> struct` in the compiler's state, and AFAIK this mapping constitutes the *identity* of the type: it's what ensures that two calls to `ArrayList(i32)` resolve to the same type. Therefore, to resolve the type `T` in `ArrayList(|T|)` at call sites, we just need to do a reverse lookup on the ArrayList identity mapping. This seems very doable to me. For example, if the identity mapping is a hashmap then we could just maintain an inverse hashmap alongside it. TL;DR: from what I can tell at a glance, `ArrayList(|T|)` and even `@Vector(|len|, i32)` looks very doable with minimal compiler complexity. But I will leave it to the Zig maintainers to determine whether this is actually the case.
Sign in to join this conversation.
No Branch/Tag specified
master
elfv2
spork8
restricted
0.16.x
panic-rewrite
parking-futex-lockfree
windows-Io-cleanup
Io-watch
ParseCommandLineOptions
windows-async-files
poll-ring
debug-file-leaks-differently
debug-file-leaks
ProcessPrng
elfv2-dyn
jobserver
threadtheft
io-threaded-no-queue
0.15.x
Io.net
comptime-allocator
restricted-function-pointers
cli
wasm-linker-writer
wrangle-writer-buffering
sha1-stream
async-await-demo
fixes
0.14.x
ast-node-methods
macos-debug-info
make-vs-configure
fuzz-macos
sans-aro
ArrayList-reserve
incr-bug
llvm-ir-nosanitize-metadata
ci-tarballs
ci-scripts
threadpool
0.12.x
new-pkg-hash
json-diagnostics
more-doctests
rework-comptime-mutation
0.11.x
ci-perf-comment
stage2-async
0.10.x
autofix
0.9.x
aro
hcs
0.8.x
0.7.x
0.16.0
0.15.2
0.15.1
0.15.0
0.14.1
0.14.0
0.12.1
0.13.0
0.12.0
0.11.0
0.10.1
0.10.0
0.9.1
0.9.0
0.8.1
0.8.0
0.7.1
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.1
0.1.0
Labels
Clear labels
abi/f32
abi/ilp32
abi/sf
accepted
This proposal is planned.
arch/21k
arch/6502
arch/aarch64
arch/alpha
arch/amdgcn
arch/arc
arch/arc32
arch/arc64
arch/arm
arch/avr
arch/bfin
arch/bpf
arch/colossus
arch/cris
arch/csky
arch/dlx
arch/epiphany
arch/fr30
arch/frv
arch/hexagon
arch/hppa
arch/hppa64
arch/ia64
arch/kalimba
arch/kvx
arch/lanai
arch/lm32
arch/loongarch32
arch/loongarch64
arch/m32r
arch/m68k
arch/m88k
arch/mcore
arch/microblaze
arch/mips
arch/mips64
arch/mmix
arch/moxie
arch/mrisc32
arch/msp430
arch/nds32
arch/ns32k
arch/nvptx
arch/or1k
arch/powerpc
arch/powerpc64
arch/propeller
arch/riscv32
arch/riscv64
arch/rl78
arch/rx
arch/s390x
arch/sh
arch/sparc
arch/sparc64
arch/spirv
arch/spu
arch/tricore
arch/v850
arch/vax
arch/vc4
arch/ve
arch/wasm
arch/x86
arch/x86_64
arch/xcore
arch/xtensa
autodoc
The web application for interactive documentation and generation of its assets.
backend/c
The C backend outputs C source code.
backend/llvm
The LLVM backend outputs an LLVM bitcode module.
backend/self-hosted
The self-hosted backends produce machine code directly.
binutils
Zig's included binary utilities: zig ar, zig dlltool, zig lib, zig ranlib, zig objcopy, and zig rc.
breaking
Implementing this issue could cause existing code to no longer compile or have different behavior.
build system
The Zig build system - zig build, std.Build, the build runner, and package management.
debug info
An issue related to debug information (e.g. DWARF) produced by the Zig compiler.
docs
An issue with documentation, e.g. the language reference or standard library doc comments.
error message
This issue points out an error message that is unhelpful and should be improved.
frontend
Tokenization, parsing, AstGen, ZonGen, Sema, Legalize, and Liveness.
fuzzing
An issue related to Zig's integrated fuzz testing.
incremental
Reuse of internal compiler state for faster compilation.
lib/c
This issue relates to Zig's libc implementation and/or vendored libcs.
lib/compiler-rt
This issue relates to Zig's compiler-rt library.
lib/cxx
This issue relates to Zig's vendored libc++ and/or libc++abi.
lib/std
This issue relates to Zig's standard library.
lib/tsan
This issue relates to Zig's vendored libtsan.
lib/ubsan-rt
This issue relates to Zig's ubsan-rt library.
lib/unwind
This issue relates to Zig's vendored libunwind.
linking
Zig's integrated object file and incremental linker.
miscompilation
The compiler reports success but produces semantically incorrect code.
os/android
os/contiki
os/dragonfly
os/driverkit
os/emscripten
os/freebsd
os/fuchsia
os/haiku
os/hermit
os/hurd
os/illumos
os/ios
os/linux
os/maccatalyst
os/macos
os/managarm
os/netbsd
os/ohos
os/openbsd
os/plan9
os/redox
os/rtems
os/serenity
os/tvos
os/uefi
os/visionos
os/wasi
os/watchos
os/windows
proposal
This issue suggests language modifications. If it also has the "accepted" label then it is planned.
release notes
This issue or pull request should be mentioned in the release notes.
testing
This issue is related to testing the compiler, standard library, or other parts of Zig.
zig cc
Zig as a drop-in C-family compiler.
zig fmt
The Zig source code formatter.
zig reduce
The Zig source code reduction tool.
bounty
https://ziglang.org/news/announcing-donor-bounties
bug
Observed behavior contradicts documented or intended behavior.
contributor-friendly
This issue is limited in scope and/or knowledge of project internals.
downstream
An issue with a third-party project that uses this project.
enhancement
Solving this issue will likely involve adding new logic or components to the codebase.
infra
An issue related to project infrastructure, e.g. continuous integration.
optimization
A task to improve performance and/or resource usage.
question
No questions on the issue tracker; use a community space instead.
regression
Something that used to work in a previous version stopped working
upstream
An issue with a third-party project that this project uses.
use case
Describes a real use case that is difficult or impossible, but does not propose a solution.
No labels
abi/f32
abi/ilp32
abi/sf
accepted
arch/21k
arch/6502
arch/aarch64
arch/alpha
arch/amdgcn
arch/arc
arch/arc32
arch/arc64
arch/arm
arch/avr
arch/bfin
arch/bpf
arch/colossus
arch/cris
arch/csky
arch/dlx
arch/epiphany
arch/fr30
arch/frv
arch/hexagon
arch/hppa
arch/hppa64
arch/ia64
arch/kalimba
arch/kvx
arch/lanai
arch/lm32
arch/loongarch32
arch/loongarch64
arch/m32r
arch/m68k
arch/m88k
arch/mcore
arch/microblaze
arch/mips
arch/mips64
arch/mmix
arch/moxie
arch/mrisc32
arch/msp430
arch/nds32
arch/ns32k
arch/nvptx
arch/or1k
arch/powerpc
arch/powerpc64
arch/propeller
arch/riscv32
arch/riscv64
arch/rl78
arch/rx
arch/s390x
arch/sh
arch/sparc
arch/sparc64
arch/spirv
arch/spu
arch/tricore
arch/v850
arch/vax
arch/vc4
arch/ve
arch/wasm
arch/x86
arch/x86_64
arch/xcore
arch/xtensa
autodoc
backend/c
backend/llvm
backend/self-hosted
binutils
breaking
build system
debug info
docs
error message
frontend
fuzzing
incremental
lib/c
lib/compiler-rt
lib/cxx
lib/std
lib/tsan
lib/ubsan-rt
lib/unwind
linking
miscompilation
os/android
os/contiki
os/dragonfly
os/driverkit
os/emscripten
os/freebsd
os/fuchsia
os/haiku
os/hermit
os/hurd
os/illumos
os/ios
os/linux
os/maccatalyst
os/macos
os/managarm
os/netbsd
os/ohos
os/openbsd
os/plan9
os/redox
os/rtems
os/serenity
os/tvos
os/uefi
os/visionos
os/wasi
os/watchos
os/windows
proposal
release notes
testing
zig cc
zig fmt
zig reduce
bounty
bug
contributor-friendly
downstream
enhancement
infra
optimization
question
regression
upstream
use case
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
34 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ziglang/zig#32099
Reference in a new issue
ziglang/zig
No description provided.
Delete branch "%!s()"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?