One halfway option in Zig (though I _think_ interfaces are still being considered for addition to the language as first-class citizens) is to use the language's metaprogramming facilities.
Somebody, once, in the userspace of the language, needs to write a utility that reads a type and produces an interface checker for that type, so that you're able to write code like the following:
const IDog = Checker(TemplateDogType);
Then you can use that when defining a function expecting to conform to some interface:
fn bark(_dog: anytype) void {
const dog: IDog(_dog) = _dog;
dog.bark();
}
You can easily get nice compiler errors, no runtime overhead, and all the usual sorts of things you expect from a simple interface system. It's just more verbose.
Limiting access to non-interface methods without runtime overhead would be a bit more cumbersome I think. Off the top of my head, the following API is possible though:
fn bark(dog: anytype) void {
IDog.bark(&dog);
}
I'm not sure I understand. Anytype is type-checked at compile time, not runtime, so you already have these things. The downside of anytype is that it's non-documenting, in the sense that you can't read the function signature and know what's expected.
The thing you gain is exactly that missing documentation (via @compileError and whatnot in the hypothetical library code I hand-waved away). The compiler errors can point you to the exact interface you're supposed to adhere to (as opposed to combatting errors one at a time), and by construction give you a non-out-of-date template to examine.
It's not perfect since it's all in the userspace of the language (it'd be nicer to be able to express an interface type in the function signature), but it solves the problem you mentioned completely.