Why does the compile treat run-time @panic calls differently from other run-time expressions?

The following code fails to compile now, because the @panic call causes control flow is diverted error at compile time. But isn’t the call a run-time expression?

var x = "abc";

pub fn main() void {
    const T = @TypeOf( @panic(x) );
    @import("std").debug.print("{}\n", .{ T });
}

It’s cuz the return type of @panic() is noreturn. The compiler knows that you’re unconditionally calling a noreturn function, so it knows by extension that control flow will never, ever reach past that function call.

2 Likes

But the @TypeOf function treats comptime and runtime code differently. If its argument is comptime, the argument will be evaluated; however if the argument is runtime, then only the type of the argument matters and the runtime argument will be ignored totally at runtime.

For example, the following code compiles:

fn foo() noreturn {
    @panic("bye");
}

pub fn main() void {
    _ = @TypeOf( { unreachable; } );
    _ = @TypeOf( { return; } );
    _ = @TypeOf( { @panic("bye"); } );
    _ = @TypeOf( foo() );
}

But the following code fails to compile:

fn foo() noreturn {
    @panic("bye");
}

pub fn main() void {
    _ = @TypeOf( unreachable );
    _ = @TypeOf( return );
    _ = @TypeOf( @panic("bye") );
    _ = @TypeOf( comptime foo() );
}

Sorry, maybe my description is not precise. It is hard to say { unreachable; } is a runtime expression but unreachable is a comptime one.

Ooh, this is an example of why they’re looking at that ‘_: |T| = @panic(x)’ syntax! It’s totally unambiguous in the new syntax that @panic is going to run, but we kind of expect @TypeOf() to skip it. (Not 100% sure that’s approved as is but the discussion was interesting.)

I like the |T| proposal, but I also like the convenience of the current @TypeOf function in many situations.

If @TypeOf is kept later, I do hope it treats comptime and runtime arguments the same: just cares about their types and never evaluated them, either at run time or compile time.

I’m fine with losing @TypeOf() if I get the unambiguous one, but I kind of see where you’re coming from. Do you have a more real-world example than this @panic example?

No currently. This one is just for explanation purpose in a book and I think there is a minor inconsistency issue here.

I indeed have many other use cases of @TypeOf in practical. For example, to get the return type of a function call without executing the call.

1 Like

Huh, I’ve always used @typeInfo on the type of the function - not the call but the function body or function pointer. I’m not sure if using it on a function call result just never occurred to me or if I misread some bug in past years as not supporting it.

Anyway, end result is I’m already using it in ways that don’t have any possible runtime ambiguity, maybe that’s why I like it better.

You can’t use @typeInfo if the result is generic.

True. I think my pattern of creating types w groups of functions works around that part so it’s another way I didn’t realize I was working around a problem.

Er, the description “if the argument of a @TypeOf function call is comptime known, it will get evaluated” is not precise. For example, the while(true) {} loop in the following code is not get executed, but the x += 1; before it gets executed.

The @TypeOf function is really special.

pub fn main() void {
    comptime var x: u8 = 0;
    const T = @TypeOf({x += 1; while(true) {}});
    x += 1;
    @import("std").debug.print("{}, {}\n", .{ x, T }); // 2 noreturn
}

It seems the compiler treats {x += 1; while(true) {}} as partially runtime and partially comptime.

This is some weird. So maybe it is a good idea to never evaluate the arguments of @TypeOf calls, for simplicity and fewer subtleties.