Metaprogramming is the root of much complexity in programming, but the main alternative is reflection. I suspect that with sufficiently advanced metaprogramming reflection is essentially never needed, but you end up with other costs.
In Rust you might find yourself deriving nearly every struct with a laundry list of traits. This is a cool feature, but I think the equivalent situation in Go is worth considering. Go doesn't have traits or macros, but it does have reflection. In effect, in Rust you can choose what is generated off of your structs, whereas in Go you get a baseline amount of generated information and you choose what to do with it at runtime.
Despite being undoubtably less efficient at runtime, it has its advantages. You don’t have to think about deriving Debug, for example, because there’s enough reflection information to do the needful at runtime instead. In Rust you may pay more executable space per struct in some cases due to having to generate lots of bits of code, whereas in Go you end up with a lot of little bits because each struct generates some data.
The important thing to think about here is that these approaches are actually not so different. They shift costs around differently, but they ultimately accomplish similar things. I think inherently the Rust approach feels much more powerful, and I think that’s a fair assessment, but the Go approach is flexible. The same struct metadata can print debug information, and not only that, it can do so in multiple formats. It can be used to serialize and deserialize data like JSON. I maintain a small library called Restruct that implements binary serialization a la Kaitai Struct that uses reflection.
Not having generics has been a huge advantage and disadvantage of Go from the beginning. Generics really do carry a lot of implications. On a sliding scale of generating interface implementations from structural information at compiletime to purely reflection based strategies, I think there is a sweet spot.
My knee jerk reaction to contracts in Go was that it sucked. It seemed like a very weak implementation of metaprogramming. However, I’ve come around to thinking it might be the way to go.
Having advanced templates would be useful too, but they would have drastic implications as well. What I like about contracts is they feel self contained. It feels like everything else around the contracts usages is fairly normal and it won’t have a strong impact on how Go is written today.
So while it won’t make a lot of people happy, it will probably help the day-to-day Go programmer a bit, by providing the ability, finally, to generalize functions. I’d argue there are rarely many circumstances where this is really needed in day to day Go programming, and therefore this fairly minimal approach shall serve the language well.
I think the future of Go and how it fits in with other modern programming languages will be firmly cemented by which approaches are chosen to solve common Go problems in Go 2 and 3, and this is certainly a big one. If you feel unsatisfied by this, I think there is still room for more new programming language research, and highly recommend people try languages like Zig, Nim and of course Rust. Go may not be your thing or maybe not suitable for your use cases, and I suspect its possible it never will be suitable for all use cases after all. The same can be said about all programming languages for the foreseeable future.
Regarding this particular draft, my only real input is that I dislike the assure syntax for some reason. It would also be great to avoid new keywords if possible, though I have no idea how you could avoid that here.