Skip to content

replace @Type with individual type-creating builtins#23733

Merged
mlugg merged 3 commits intoziglang:masterfrom
alichraghi:bp
Nov 23, 2025
Merged

replace @Type with individual type-creating builtins#23733
mlugg merged 3 commits intoziglang:masterfrom
alichraghi:bp

Conversation

@alichraghi
Copy link
Contributor

@alichraghi alichraghi commented Apr 30, 2025

Closes #10710

I updated zig1.wasm in first commit to ensure the CI is passing. once ready, one of the core team members can handle updating it.

I also didn't add @Array because as @Hejsil pointed out, it can be implemented in a single line of user-land code.

if (sentinel) |sen| [size:sen]T else [size]T

Same goes for @Float. std.meta.Float() is no longer a wrapper of @Type.

@alichraghi alichraghi force-pushed the bp branch 2 times, most recently from 687062e to c7bb559 Compare April 30, 2025 18:03
@SoundOfScooting
Copy link

Although it was not listed in the issue, would it be reasonable to add @Fn as well?

@nitrogenez
Copy link

Seems like a good change. I like it. Though, is it necessary to remove @Type altogether? Can't these builtins co-exist? Or is there some deep technical reason they can't?

@linusg
Copy link
Collaborator

linusg commented May 6, 2025

Though, is it necessary to remove @Type altogether?

"Only one obvious way to do things."

@nitrogenez
Copy link

"Only one obvious way to do things."

Oh, yeah. That makes sense. Thanks. Then, I don't see why this change can't be accepted. Seems pretty useful and straightforward.

@tauoverpi
Copy link
Contributor

Does @Struct(..), @Union(..), and @Enum(..) really need to use the Type.{Struct, Union, Enum} types or is this for it being easier to use with the result of @typeInfo(..)?

Each of the fields of those are of different types thus @Struct(.@"extern", fields, .@"struct") would better match what the use is today (I've omitted the .decl field as it's always empty anyway) or just @Struct(.@"extern", fields) and @Tuple(fields) as even if they share syntax and representation in the compiler they aren't the same from the user's view of the language. The same applies to @Union(.@"auto", E, fields)/@Union(.@"extern", null, fields) and @Enum(fields, .sparse)/@Enum(fields, .exhaustive).

It's not possible to pass the wrong type for any of those and if the bools were instead changed to enum it would communicate what the parameters are sufficiently enough not to need the Type.{..} struct.

@mlugg
Copy link
Member

mlugg commented Jun 3, 2025

Someone asked me about the status of this PR, so just commenting to say that I'm aware of it, and appreciate the effort that went into it -- I'd definitely like to avoid letting this work rot. I'll hopefully get to it soon, but we have some huge changes currently in the pipeline, so the team's all a little busy right now, and it's not a great time to merge big breaking PRs. I'm hoping I'll be able to have a look at this once the reader/writer changes, and codegen pipeline changes, are all in, and everything's settling down a little.

No need to worry about rebasing or anything, I will probably be happy to resolve conflicts when I get around to reviewing this.

One note: I am firmly of the opinion that there should be an @EnumLiteral(), rather than relying on @TypeOf(.enum_literal). That said, I can make that change on top of this PR myself fairly easily when I review it.

By the way, @alichraghi: were the usages of @Type all updated by hand, or did you semi-automate the process? Knowing this will probably help the review process when the time comes.

@alichraghi
Copy link
Contributor Author

Yeah i figured that this is gonna take long and im fine with handling conflicts myself. was mostly waiting for writergate branch to be merged.

were the usages of @Type all updated by hand, or did you semi-automate the process? Knowing this will probably help the review process when the time comes.

They are updated by hand but it wasn't painful because most new builtins accept the same std.builtin.Type parameters.

@alichraghi alichraghi force-pushed the bp branch 2 times, most recently from 3d17739 to 9a324c7 Compare June 15, 2025 09:06
@mlugg mlugg added breaking Implementing this issue could cause existing code to no longer compile or have different behavior. release notes This PR should be mentioned in the release notes. labels Jul 12, 2025
@alichraghi alichraghi force-pushed the bp branch 2 times, most recently from 506bcef to c69419b Compare October 10, 2025 11:48
Copy link
Member

@mlugg mlugg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not push changes to address any of these comments! In a moment, I'm going to pull this branch, rebase it, resolve all of my comments. and force-push a merge-ready version (though the push probably won't be til tomorrow). However, feel free to read through the comments if you're interested to see what I'm going to change.

Thanks for this work!

@mlugg
Copy link
Member

mlugg commented Nov 22, 2025

I know that push took a bit longer than I claimed, but there's good reason for it!

I rebased this work and handled my comments yesterday, but after discussing the change with Andrew, I ended up deciding to make some more changes. @EnumLiteral and @Int are unchanged, but the other builtins have had their usages updated to be much friendlier to use (which is made possible by splitting up @Type). I've also introduced @Fn (there were a few usages of function type reification in the wild, and I believe there may be reasonable use cases for it) and @Tuple (split out from @Struct).

You can look at the last commit here to see what the new usages look like in practice. Also, the first commit does update the language reference to include correct signatures for all of the new builtins. The arguments are given in SoA form (e.g. there is a slice of field names and a separate slice of field types); this might feel awkward if you try to write an invocation using only literals, but since these builtins are only actually used when you need to programmatically create types, it turns out to work very nicely.

The builtins I have implemented here are currently accepted; assuming this passes CI, it will be merged soon.

@mlugg
Copy link
Member

mlugg commented Nov 22, 2025

okay apparently i failed to actually test building the langref

mlugg and others added 3 commits November 22, 2025 22:42
The new builtins are:
* `@EnumLiteral`
* `@Int`
* `@Fn`
* `@Pointer`
* `@Tuple`
* `@Enum`
* `@Union`
* `@Struct`

Their usage is documented in the language reference.

There is no `@Array` because arrays can be created like this:

    if (sentinel) |s| [n:s]T else [n]T

There is also no `@Float`. Instead, `std.meta.Float` can serve this use
case if necessary.

There is no `@ErrorSet` and intentionally no way to achieve this.
Likewise, there is intentionally no way to reify tuples with comptime
fields, or function types with comptime parameters. These decisions
simplify the Zig language specification, and moreover make Zig code more
readable by discouraging overly complex metaprogramming.

Co-authored-by: Ali Cheraghi <alichraghi@proton.me>
Resolves: ziglang#10710
Signed-off-by: Matthew Lugg <mlugg@mlugg.co.uk>
Co-authored-by: Matthew Lugg <mlugg@mlugg.co.uk>
@mlugg mlugg enabled auto-merge November 23, 2025 00:21
@mlugg mlugg merged commit 6d543bc into ziglang:master Nov 23, 2025
9 checks passed
@mlugg
Copy link
Member

mlugg commented Nov 23, 2025

This needs release notes; I'll try to write some up today, since they'll also be useful for people tracking master to migrate.

@Mulling
Copy link

Mulling commented Nov 26, 2025

What is the rationale for not using StructField in @Struct?

<p>Returns an integer type with the given signedness and bit width.</p>
<p>For instance, {#syntax#}@Int(.unsigned, 18){#endsyntax#} returns the type {#syntax#}u18{#endsyntax#}.</p>
{#header_close#}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builtin Functions should be arranged in alphabetical order.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not true. While this list is broadly alphabetical, we deviate from that where it makes sense for organizational purposes; for instance, @sin, @cos, and @tan are grouped together.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For what it's worth, I appreciated that they were all in one place :)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not true. While this list is broadly alphabetical, we deviate from that where it makes sense for organizational purposes; for instance, @sin, @cos, and @tan are grouped together.

You are right. Sorry :)

@mlugg
Copy link
Member

mlugg commented Nov 26, 2025

What is the rationale for not using StructField in @Struct?

The SoA style here (i.e. separate arrays for each "property" of fields) is far more convenient to use in essentially all cases. It particularly shines in several extremely common cases:

  • The field "attributes" (alignment, comptime-ness, default value) are all the default (natural alignment, non-comptime, no default); in that case, you do @Struct(.auto, null, names, types, &@splat(.{})) rather than having to explicitly set them for every field
  • Even better, the field types are all the same too, in which case you just do @Struct(.auto, null, names, &@splat(FieldType), &@splat(.{}))
  • The field names come from something else, e.g. the fields of an enum; you can just do @Struct(.auto, null, std.meta.fieldNames(MyEnum), &@splat(FieldType), &@splat(.{})) instead of having to convert EnumField to StructField in a loop (which kind of sucks to do)

I can't be sure, but I would anticipate StructField being removed, and std.builtin.Type.Struct instead being changed to report fields in SoA style (having separate slices for field names, field types, etc).

@mlugg
Copy link
Member

mlugg commented Nov 29, 2025

Release notes below.

@Type replaced with individual type-creating builtin functions

Zig 0.16.0 implements long-accepted proposal #10710 to remove the @Type builtin from the language and replace it with individual builtins like @Int and @Struct. While @Type is a simple parallel to @typeInfo, in practice, it was clunky to use for common tasks, leading users to reach for helpers like std.meta.Int. Ignoring @Vector, which already existed, @Type has been replaced with 8 new builtin functions:

@EnumLiteral() type

@Int(comptime signedness: std.builtin.Signedness, comptime bits: u16) type

@Tuple(comptime field_types: []const type) type

@Pointer(
    comptime size: std.builtin.Type.Pointer.Size,
    comptime attrs: std.builtin.Type.Pointer.Attributes,
    comptime Element: type,
    comptime sentinel: ?Element,
) type

@Fn(
    comptime param_types: []const type,
    comptime param_attrs: *const [param_types.len]std.builtin.Type.Fn.Param.Attributes,
    comptime ReturnType: type,
    comptime attrs: std.builtin.Type.Fn.Attributes,
) type

@Struct(
    comptime layout: std.builtin.Type.ContainerLayout,
    comptime BackingInt: ?type,
    comptime field_names: []const []const u8,
    comptime field_types: *const [field_names.len]type,
    comptime field_attrs: *const [field_names.len]std.builtin.Type.StructField.Attributes,
) type

@Union(
    comptime layout: std.builtin.Type.ContainerLayout,
    /// Either the integer tag type, or the integer backing type, depending on `layout`.
    comptime ArgType: ?type,
    comptime field_names: []const []const u8,
    comptime field_types: *const [field_names.len]type,
    comptime field_attrs: *const [field_names.len]std.builtin.Type.UnionField.Attributes,
) type

@Enum(
    comptime TagInt: type,
    comptime mode: std.builtin.Type.Enum.Mode,
    comptime field_names: []const []const u8,
    comptime field_values: *const [field_names.len]TagInt,
) type

Enum Literal

@EnumLiteral() returns the "enum literal" type, which is the type of uncoerced enum literals like .foo. While it is equivalent to @TypeOf(.something), the new @EnumLiteral() is preferred for consistency.

@Type(.enum_literal)

->

@EnumLiteral()

Integer

@Int is perhaps the most useful new builtin for simple metaprogramming. The usage is equivalent to the now-deprecated std.meta.Int helper: given a signedness and bit count, it returns an integer type with those properties. This new usage results in significantly more concise and readable code.

@Type(.{ .int = .{ .signedness = .unsigned, .bits = 10 } })

->

@Int(.unsigned, 10)

Tuple

@Tuple is equivalent to the now-deprecated std.meta.Tuple helper. It accepts a slice of types, and returns a tuple type whose fields have those types.

@Type(.{ .@"struct" = .{
    .layout = .auto,
    .fields = &.{.{
        .name = "0",
        .type = u32,
        .default_value_ptr = null,
        .is_comptime = false,
        .alignment = @alignOf(u32),
    }, .{
        .name = "1",
        .type = [2]f64,
        .default_value_ptr = null,
        .is_comptime = false,
        .alignment = @alignOf([2]f64),
    }},
    .decls = &.{},
    .is_tuple = true,
} })

->

@Tuple(&.{ u32, [2]f64 })

To simplify the language, it is no longer possible to reify tuple types with comptime fields.

Pointer

@Pointer returns a pointer type, equivalent to @Type(.{ .pointer = ... }). Notably, it uses the new std.builtin.Type.Pointer.Attributes type, which uses struct field default values to make the usage more concise and more closely aligned with literal pointer type syntax.

@Type(.{ .pointer = .{
    .size = .one,
    .is_const = true,
    .is_volatile = false,
    .alignment = @alignOf(u32),
    .address_space = .generic,
    .child = u32,
    .is_allowzero = false,
    .sentinel_ptr = null,
} })

->

@Pointer(.one, .{ .@"const" = true }, u32, null)
@Type(.{ .pointer = .{
    .size = .many,
    .is_const = false,
    .is_volatile = false,
    .alignment = 1,
    .address_space = .generic,
    .child = u64,
    .is_allowzero = false,
    .sentinel_ptr = &@as(u64, 0),
} })

->

@Pointer(.many, .{ .@"align" = 1 }, u64, 0)

Function

@Fn returns a function type, equivalent to @Type(.{ .@"fn" = ... }). Like for pointers, new helper types have been introduced to make this builtin simpler to use. Parameters are specified with two separate arguments: the first specifies all parameter types, and the second specifies "attributes" (which currently consist only of the noalias flag).

@Type(.{ .@"fn" = .{
    .calling_convention = .c,
    .is_generic = false,
    .is_var_args = true,
    .return_type = u32,
    .params = &.{.{
        .is_generic = false,
        .is_noalias = false,
        .type = f64,
    }, .{
        .is_generic = false,
        .is_noalias = true,
        .type = *const anyopaque,
    }},
} })

->

@Fn(
    &.{ f64, *const anyopaque },
    &.{ .{}, .{ .@"noalias" = true } },
    u32,
    .{ .@"callconv" = .c, .varargs = true },
)

This is one of several of the new builtins which accepts arguments in a "struct of arrays" style. An advantage of this style is that it makes it easy to specify a fixed value for all elements. For instance, to use the "default" attributes .{} for all parameters, use &@splat(.{}):

@Fn(param_types, &@splat(.{}), ReturnType, .{ .@"callconv" = .c })

Struct

@Struct returns a struct type, equivalent to @Type(.{ .@"struct" = ... }). Like @Fn, it uses a "struct of arrays" strategy to pass information about fields. Fields are passed as three separate arrays---field names, field types, and field attributes---where the latter includes alignment, the comptime flag, and the field's default value (if any).

@Type(.{ .@"struct" = .{
    .layout = .@"extern",
    .fields = &.{.{
        .name = "foo",
        .type = [2]f64,
        .default_value_ptr = null,
        .is_comptime = false,
        .alignment = 1,
    }, .{
        .name = "bar",
        .type = u32,
        .default_value_ptr = &@as(u32, 123),
        .is_comptime = true,
        .alignment = @alignOf(u32),
    }},
    .decls = &.{},
    .is_tuple = false,
} })

->

@Struct(
    .@"extern",
    null,
    &.{ "foo", "bar" },
    &.{ [2]f64, u32 },
    &.{
        .{ .@"align" = 1 },
        .{ .@"comptime" = true, .default_value_ptr = &@as(u32, 123) },
    },
)

Again, &@splat(.{}) is useful for specifying "default" field attributes. In some cases, it is even useful to use @splat for the field types. For instance, to create a struct with homogeneous field types of FieldType where the field names match the names of an enum type MyEnum:

const MyStruct = @Struct(.auto, null, std.meta.fieldNames(MyEnum), &@splat(FieldType), &@splat(.{}));

Union

@Union returns a union type, equivalent to @Type(.{ .@"union" = ... }). It is quite similar to @Struct in usage.

@Type(.{ .@"union" = .{
    .layout = .auto,
    .tag_type = MyEnum,
    .fields = &.{.{
        .name = "foo",
        .type = i64,
        .alignment = @alignOf(i64),
    }, .{
        .name = "bar",
        .type = f64,
        .alignment = @alignOf(f64),
    }},
    .decls = &.{},
} })

->

@Union(
    .auto,
    MyEnum,
    &.{ "foo", "bar" },
    &.{ i64, f64 },
    &@splat(.{}),
)

Enum

@Enum returns an enum type, equivalent to @Type(.{ .@"enum" = ... }). It is somewhat similar to @Struct in usage, but accepts an array of field tag values rather than field types.

@Type(.{ .@"enum" = .{
    .tag_type = u32,
    .fields = &.{.{
        .name = "foo",
        .value = 0,
    }, .{
        .name = "bar",
        .value = 1,
    }},
    .decls = &.{},
    .is_exhaustive = true,
} })

->

@Enum(
    u32,
    .exhaustive,
    &.{ "foo", "bar" },
    &.{ 0, 1 },
)

Float

There is no @Float builtin, because there are only 5 runtime floating-point types, so this functionality is trivially implemented in userland. The function std.meta.Float can be used if creating float types from a bit count is required.

Array

There is no @Array builtin, because this functionality is trivial to implement with normal array syntax. A general Array function would look like this:

fn Array(comptime len: usize, comptime Elem: type, comptime sentinel: ?Elem) type {
    return if (sentinel) |s| [len:s]Elem else [len]Elem;
}

In practice, this generality is not usually necessary, and use sites can simply be replaced with one of [len]Elem or [len:s]Elem.

Opaque

There is no @Opaque builtin. Instead, write opaque {}.

Optional

There is no @Optional builtin. Instead, write ?T.

Error Union

There is no @ErrorUnion builtin. Instead, write E!T.

Error Set

There is no @ErrorSet builtin. To simplify the language, it is no longer possible to reify error sets. Instead, declare your error sets explicitly using error{ ... } syntax.

@alichraghi alichraghi deleted the bp branch December 2, 2025 03:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Implementing this issue could cause existing code to no longer compile or have different behavior. release notes This PR should be mentioned in the release notes.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

remove @Type from the language, replacing it with individual type-creating builtins

9 participants