Since *Opaque automatically coerces to *anyopaque on assignment, it feels like *const fn (*Opaque) void should also automatically coerce to *const fn (context: *anyopaque) void
I feel like this would be such a good QoL without any tradeoffs when dealing with vtables. Is there some reason why this coercion should not be done automatically? Maybe too complex on the compiler side?
I’m not sure this should be brought in, the argument is basically that because it’s implicit conversion as a pointer, it should be coerced also when its a function parameter, but then why not just allow arbitrary pointer conversions? I am much more likely to have a VTable pointers with the actual straight type like *const fn (self: *Self) void, and saving the overhead of one very specific @ptrCast is not useful enough to add language rules in my opinion.
A more general rule where any pointer parameter could be implicitly cast to an anyopaque pointer (would save on the @ptrCast in all the other cases as well) could be cool.
What you are saying makes sense so that I was wondering why I didn’t think of that, but you can’t cast anyopaque to non-opaque as trivially as to opaque due to alignment issues. Wnich is why you will often also see @alignCast in the vtable implementations. So I can see why zig devs wouldn’t want to do that implicitly. Afaik anyopaque and opaque will always have the same alignment (of 1), so I see no issue there.
I agree it’s annoying when writing vtable, but from a type theory perspective it’s correct. The vtable wants a function that work with any ptr, your function only handles *Opaque.
Note that opaque creates a new struct so const A = opaque{} is different from const B = opaque
That the vtable wants a function that works with any ptr sounds like very strange explanation to me - you can not reasonably expect the function to work with any pointer most of the times.
The way I see it is that the vtable wants a function that works with a pointer with alignment of 1, of which type the vtable does not care/know about. And so any pointer with alignment of 1 should work as a parameter instead of the *anyopaque - that just feels the most natural to me.
Also note that the type of functions seems to be structural, unlike structures - if you define type of function twice with the same signature, it is in fact the same type.
So this just works even though the function type is defined twice:
So if the type is structural, than if I can assign each component of type B to respective component of type A, then I should be able to assign type B to type A and so *const fn (context: *Opaque) void should be assignable to *const fn (context: *anyopaque) void because *Opaque is assignable to *anyopaque. Or is there something I am not understanding correctly?
Is it not simply that you’re trying to do the inverse to what you’re saying? Yes you can cast an *Opaque to an *anyopaque, but you’re trying to set something up that would implicitly do the opposite - take an *anyopaque on the vtable and then cast it to an *Opaque for your function.
Well from the standpoint of the caller, it is still just *anyopaque, so no casting at all. And from the standpoint of the one who passes in the function, you are replacing *anyopaque with *Opaque, so that feels explicit enough to me to warrant implicit cast.
The type theory issue is called covariance vs contra-variance.
If type A is a subtype of type B then fn () A is a subtype of fn () B. And fn (B) void is a subtype of fn (A) void not the other way around.
So I don’t think the conversion you propose make sense at the language level. That said I already wrote helpers to instantiate vtable that takes the specific pointee type T and a function taking a *T and returning a function using anyopaque
As others mention there are cases where it’s unsafe to have an implicit cast.
Zig generally prefers explicitness in that case. And since it can be easily implemented in user space, you will have a hard time convincing them to add it to the language.
It could probably make sense in the standard library, but until then it’s not too difficult to implement yourself (at least everything you need for self pointer and self return values) if you know your way around comptime or you can just copy it from an existing project.