Type.Bottom

Continuing the discussion from Code review: initialization via tuple of tuples:

Zig has a type system the way C does (but much better designed): the focus is on defining how memory is used, and establishing how that memory gets into and out of functions. So it doesn’t need a ‘top’ type, like a type-of-all-types, because it doesn’t have a type hierarchy, just some useful rules for when the compiler allows you to coerce one type into another. anytype doesn’t really function like a top type, it’s more of a placeholder: “any type can go here, stick something in this slot and see if the function compiles”. type isn’t a top type either, that’s a specific tagged union called Type, which exists at comptime and can be used accordingly. Zig’s type system is engineering, not mathematics.

But the type-of-no-types would be helpful, specifically so that @TypeOf(a, b) could return a value when a and b have no type they mutually coerce to. This would just be a branch of the tagged union, Type.Bottom would be traditional, but Type.NoType would work just as well. The essence of what a bottom type is, is that it’s what the type system returns when a value can’t inhabit the type which is demanded by a statement.

It isn’t necessary that @TypeOf(a_u8_slice, a_bool) throws an error, this just means that type reflection has to be careful about checking if a peer coercion is possible.

Because it isn’t even catchable:

test "bottom type" {
    const a: []const u8 = "abcd";
    const b: bool = true;
    _ = @TypeOf(a, b) catch |e| {
        std.debug.print("{!}", .{e});
    };
}

This is just a compile error, making this kind of reflection very difficult.

1 Like

Other relevant analogues: the use of 0 in counting things, even though it doesn’t make sense to have zero of anything. And the use of std::monostate in C++, even though it doesn’t make sense to use C++.

1 Like

The type you’re describing is noreturn.

3 Likes

That occurred to me, yes.

The issue I saw there is this:

When resolving types together, such as if clauses or switch prongs, the noreturn type is compatible with every other type.

There is, so far as I know, no way to create an instance of type noreturn, just an expression which ‘returns’ that type. So unless I’m missing a trick, there’s no current way to use @TypeOf(a, b) to check peer coercion of a noreturn value, since those don’t exist. But the description lead me to think that for all T, @TypeOf(instance<T>, instance<noreturn>) would be T, which doesn’t quite fit the bill.

So yes, noreturn is a bottom type by any reasonable measure, but it’s an inhabited one.

If you think that noreturn is a reasonable return value for calling @TypeOf(a, b) on values which have no common peer resolution, then that’s great, the Type.Bottom I’ve been talking about already has a name, Type.NoReturn, and providing this affordance would be a smaller change. If the type system already represents a contradictory peer type coercion as Type.NoReturn, then this is the obvious choice.

I don’t know enough about the mechanics of semantic analysis in Zig to say anything intelligent about the consequences of doing it that way, vs. having a, let’s say Type.Empty now, which is by definition uninhabited, and exists only as the return value of incompatible type coercion. But if it works, that’s great! Only add complexity when you need it.