I think another other route is to provide lots of tooling to inspect the object graph -- include a pretty print function in the interface or something to that effect.
There's equivalency between the two, the struct with pointers to implementation functions can implement an interface. Likewise, the implementation functions may call other implementations, so you haven't exactly solved the proxy problem, the state controlling which implementations becomes function local instead of on a struct. Unless you start to provide a struct with all of the context, at which point you've actually re-invented interfaces. I've grown wary of having too much of the details as function local like this because it hinders making more advanced tooling by hiding intermediate state from reflection.
The interface provides the means to give other representations, and allows for embedding one within another, and gives runtime tooling to inspect underlying types. It gives more elegance and flexibility to the problem at language and standard library scale.
One of the nice things is that it gives a sort of bidirectional refactoring path. If someone chose struct-of-pointers and it's at it's breaking point, those structs can stay as is and start to implement the interface with minimal effort. Likewise, if I need to boil down a hodgepodge of implementations into a really simple control flow, struct methods capture the struct and can be passed around as ordinary function pointers, so I can built a struct out of existing interface implementations.
There's no real way around the complexity -- leaky abstractions are a thing. I think the author got it right: use interface upgrades and proxies sparingly. If they are the right tool for the job, then it's probably a good idea to go the extra mile to provide good documentation and instrumentation with extra attention to caller/callee expectations.