I work on Rust, which is in a similar space to Go. IMHO this article is oversimplifying.
(1) Recursive anonymous functions: This is the first time I've heard this criticism. MLs usually don't have this functionality either. It's not too much trouble to give the function a name.
(2) Tail call optimization: TCO isn't free. It requires all callees to pop their arguments, which slightly penalizes every call in the program. Rust is planned to support full TCO, but the current compiler only does sibling call optimization (a subset of TCO).
(3) Continuations (I assume call/cc is meant here): Again, these are expensive, because they interfere with the stack discipline. You end up having to garbage collect stack frames in the worst case. For this reason both Go and Rust have implemented exceptional control flow using stack unwinding and destructors. (For Go panic/defer/recover play the role of destructors, while for Rust we use a more traditional RAII system.)
(1) MLs allow to nest named functions. Go only allows named global functions, and lacks a feature that Pascal had.
(2) Someone has to do cleanup anyhow. Either the caller or callee. (I am no expert in calling conventions) Anyhow, isn't this practically free, because the stack is in the L1 cache?
(3) To implement lightweight threads (ala. Erlang, Go, Stackless) you already can't allocate your activation records on the C-stack. So the main cost for call/cc is already paid for. Besides, Go lacks any form of non-local jumps, so escape continuations alone (setjmp/longjmp) would already be an improvement.
Can you elaborate on how popping arguments penalizes every call? It seems like, for non-variadic calls at least, it should take the same amount of time to add a constant to %esp whether you're doing it in the RET instruction or in the caller; but factoring that code into the callee should improve code density and therefore icache hit rates. What am I missing?
Callers have to readjust the stack after each call when TCO is enabled, but without TCO a function can allocate outgoing argument space for every function invocation up front and reuse that space for every call.
(1) Recursive anonymous functions: This is the first time I've heard this criticism. MLs usually don't have this functionality either. It's not too much trouble to give the function a name.
(2) Tail call optimization: TCO isn't free. It requires all callees to pop their arguments, which slightly penalizes every call in the program. Rust is planned to support full TCO, but the current compiler only does sibling call optimization (a subset of TCO).
(3) Continuations (I assume call/cc is meant here): Again, these are expensive, because they interfere with the stack discipline. You end up having to garbage collect stack frames in the worst case. For this reason both Go and Rust have implemented exceptional control flow using stack unwinding and destructors. (For Go panic/defer/recover play the role of destructors, while for Rust we use a more traditional RAII system.)