I never got around to replying to this, but on the off-chance you're still following this thread (and any other readers still reading)
> This is not entirely true. Refs on lower level are pointers - when it compiles down to CIL, the resulting types are rendered as T&, and referred to as managed pointer, as opposed to T* the unmanaged pointer.
Right and I don't think anybody would disagree that CIL is not a low-level language. But plenty of high-level language features compile down to just pointers. This doesn't mean that the language offers pointers though! In that world I could say that Python offers pointers (or indeed any high-level language that compiles to LLVM and has a feature that just corresponds to a pointer).
> But since then, we've got the ability to return refs, declare local ref variables and fields (in ref structs), and reassign them. So at this point they're effectively types, with some restrictions on use similar to Rust borrowing.
No they're not. You still have the `Ref<...>` vs `ref` distinction (which is why ref fields aren't a thing), you still can't have nested `ref`s, and you still can't switch between "ref"ing and "unref"ing something, i.e. ref-ing is infectious.
Likewise the fact that refs have some linear characteristics doesn't make them pointers (in the same way that Linear Haskell doesn't mean that Haskell now has pointers). The reason why we say that Rust has pointers (apart from the raw pointers), is that they don't have any of these issues!
> To implement goroutines with CPS, you'd need to rewrite every call into CPS-style. This is technically not observable, yes; but perf would be atrocious in practice, so it's not going to happen.
Yes there are sound reasons for why Go has chosen certain implementation decisions, but those are not part of the language semantics, which is ultimately what determines whether a language feels high-level or low-level.
> And running that on top of green threads is possible but pointless, since the semantics of await requires that async context switching happens only at awaits; but the ability to pre-empt at any arbitrary point is precisely the advantage of green threads.
No current industrial green thread implementation I know of offers pre-emption, they are AFAIK all cooperative. IIRC Go's implementation yields on channel sends and GHC's implentation yields on memory allocation. I think Loom is heading for pre-emption, but it hasn't been finalized yet. But that's not really the point. My point is special syntax for a concurrency feature that is not backed by system threads does not preclude a language from using system threads, which is the fundamental relationship between Go's goroutines and any FFI that would expose system threads. Or put another way, being unable to use system threads are not a fundamental part of the semantics of Go, while being unable to use pointers are a fundamental part of the semantics of safe C#.