Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eliminate anytype fields from the language #10705

Closed
andrewrk opened this issue Jan 27, 2022 · 10 comments
Closed

eliminate anytype fields from the language #10705

andrewrk opened this issue Jan 27, 2022 · 10 comments
Labels
accepted This proposal is planned. breaking Implementing this issue could cause existing code to no longer compile or have different behavior. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@andrewrk
Copy link
Member

andrewrk commented Jan 27, 2022

Motivation

Simplify the language. Most people probably aren't even aware that this is possible:

const S = struct {
    x: anytype,
};

Similarly, the simplified language will be simpler to implement compilers for. Such as, you know, the one that is the main focus of the 0.10.0 milestone.

Proposed changes

Mainly, make it a compile error to use anytype as a field name. However this poses some problems to solve, such as how to deal with @typeInfo for various things. For example, the sentinel field of pointers:

https://github.com/ziglang/zig/blob/0.9.0/lib/std/builtin.zig#L239-L252

Likewise the sentinel field of arrays, the default_value field of structs, and the function field of async frames. But that's it. I can't find any other dependencies on this feature in the entire standard library.

In order to address this problem, this proposal suggests to use compile-time type erasure by using ?*const anyopaque instead of anytype. This is something that the language is already required to support, so we are exploiting it here rather than introducing yet another way of doing the same thing via anytype struct fields.

--- a/lib/std/builtin.zig
+++ b/lib/std/builtin.zig
@@ -222,11 +222,10 @@ pub const TypeInfo = union(enum) {
         child: type,
         is_allowzero: bool,
 
-        /// This field is an optional type.
         /// The type of the sentinel is the element type of the pointer, which is
         /// the value of the `child` field in this struct. However there is no way
-        /// to refer to that type here, so we use `anytype`.
-        sentinel: anytype,
+        /// to refer to that type here, so we use pointer to anyopaque.
+        sentinel: ?*const anyopaque,
 
         /// This data structure is used by the Zig language code generation and
         /// therefore must be kept in sync with the compiler implementation.
@@ -244,11 +243,10 @@ pub const TypeInfo = union(enum) {
         len: comptime_int,
         child: type,
 
-        /// This field is an optional type.
         /// The type of the sentinel is the element type of the array, which is
         /// the value of the `child` field in this struct. However there is no way
-        /// to refer to that type here, so we use `anytype`.
-        sentinel: anytype,
+        /// to refer to that type here, so we use a pointer to anyopaque.
+        sentinel: ?*const anyopaque,
     };
 
     /// This data structure is used by the Zig language code generation and
@@ -264,7 +262,7 @@ pub const TypeInfo = union(enum) {
     pub const StructField = struct {
         name: []const u8,
         field_type: type,
-        default_value: anytype,
+        default_value: ?*const anyopaque,
         is_comptime: bool,
         alignment: comptime_int,
     };
@@ -364,7 +362,7 @@ pub const TypeInfo = union(enum) {
     /// This data structure is used by the Zig language code generation and
     /// therefore must be kept in sync with the compiler implementation.
     pub const Frame = struct {
-        function: anytype,
+        function: ?*const anyopaque,
     };
 
     /// This data structure is used by the Zig language code generation and

Example of printing the default value for the first field of a struct:

const S = struct {
    x: i32 = 1234,
};

test "example" {
    const field = @typeInfo(S).Struct.fields[0];
    const default_value = @ptrCast(*const field.type, field.default_value.?).*;
    @compileLog(default_value);
}

Example of constructing an array type:

test "example" {
    const Array = @Type(.{ .Array = .{
        .len = 10,
        .child = i32,
        .sentinel = &@as(i32, 1234),
    });
    @compileLog(Array);
}

Related Issues:

@andrewrk andrewrk added breaking Implementing this issue could cause existing code to no longer compile or have different behavior. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. labels Jan 27, 2022
@andrewrk andrewrk added this to the 0.10.0 milestone Jan 27, 2022
@Hejsil
Copy link
Contributor

Hejsil commented Jan 27, 2022

There is also the question of how to generate types with sentinel and default fields values using @Type

@andrewrk
Copy link
Member Author

There is also the question of how to generate types with sentinel and default fields values using @Type

Ah right, thank you. Amended.

@marler8997
Copy link
Contributor

marler8997 commented Jan 27, 2022

The @Type construct feels simpler to me than creating builtins for specific use cases. It's the inverse of @typeInfo and feels natural to have both. I agree it's "clunky" but that seems like it could be solved with helper functions in std.meta rather than language builtins. Otherwise, if we considered the reverse, would it be simpler to have a @ptrInfo @intInfo rather than one @typeInfo?

@InKryption
Copy link
Contributor

InKryption commented Jan 28, 2022

Maybe the current rendition of @Type is overreaching; is there a use case for being able to retify void, noreturn, undefined and null? Maybe each class of type should have its own retification builtin, which would make this a fair bit more consistent, and remove these unnecessary edge cases.

Edit: Thinking about it further, is there even a need for being able to retify opaque types with a builtin? The only significant information about an opaque type is its declarations, which can't actually be retified (and afaik isn't planned to be possible?). If you want to generate a unique opaque type with no declarations, you can just write opaque {}.

@andrewrk
Copy link
Member Author

If you read this proposal before this comment, read it again because I changed everything (except for the main goal of eliminating anytype fields from the language).

@andrewrk
Copy link
Member Author

andrewrk commented Jan 28, 2022

is there even a need for being able to retify opaque types with a builtin?

no, there is not. opaque{} is the preferred way to create a new opaque type. Many uses of @Type are redundant:

  • @Type(.Void) should be avoided in favor of void
  • @Type(.Type) should be avoided in favor of type
  • @Type(.Bool) should be avoided in favor of bool
  • @Type(.NoReturn) should be avoided in favor of noreturn.
  • @Type(.ComptimeFloat) should be avoided in favor of comptime_float
  • @Type(.ComptimeInt) should be avoided in favor of comptime_int
  • @Type(.Undefined) is equivalent to @TypeOf(undefined) and it is unclear which is preferred, and that is a problem.
  • @Type(.Null) is equivalent to @TypeOf(null) and it is unclear which is preferred, and that is a problem.
  • @Type(.EnumLiteral) is equivalent to @TypeOf(.foo) and I guess the @Type one is slightly better.
  • @Type( .{ .Optional = .{ .child = T } }) should be avoided in favor of ?T.
  • @Type( .{ .ErrorUnion = .{ .error_set = E, .payload = T } }) should be avoided in favor of E!T.
  • @Type(.BoundFn) should be avoided
  • @Type(.{ .Vector = .{ .len = len, .child = T }}) sucks, @Vector(len, child) is way better. and please don't suggest to reach into std.meta to use basic primitive types of the language.

@daurnimator
Copy link
Contributor

daurnimator commented Jan 28, 2022

  • @Type(.Void) should be avoided in favor of void

Would that also be equivalent to @TypeOf(void)? or even @TypeOf({})?

@InKryption
Copy link
Contributor

@daurnimator

  • @Type(.Void) should be avoided in favor of void

Would that also be equivalent to @TypeOf(void)? or even @TypeOf({})?

@TypeOf(void) is type.

@ikskuh
Copy link
Contributor

ikskuh commented Jan 28, 2022

Using ?*const anyopaque, is a way better and less hacky solution than using a anytype field. I really like this change, especially as it makes clear if we have an optional initialized to null or no default value:

.{ .name = "a", .type = ?u32, .default_value = null },
.{ .name = "b", .type = ?u32, .default_value = &@as(?u32, null) },

@Hejsil
Copy link
Contributor

Hejsil commented Jan 28, 2022

Getting the default value needs an @alignCast as well, right?

const default_value = @ptrCast(*const field.type, @alignCast(@alignOf(field.type), field.default_value.?)).*;

Could probably use a helper method for doing this :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted This proposal is planned. breaking Implementing this issue could cause existing code to no longer compile or have different behavior. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

6 participants