4 ms·
C# is an interesting case. You can only use pointers in unsafe C# code, so you could argue that unsafe C# code is as low-level as Go or even lower (which I actu
by dwohnitmok 5y ago
C# is an interesting case. You can only use pointers in unsafe C# code, so you could argue that unsafe C# code is as low-level as Go or even lower (which I actually think you'll find many supporters for), but unsafe C# is different from normal C# and is outside the purview of everyday C# code (whereas again pointers in Go are absolutely pedestrian).
RE threading, green threads though are coming (back!) to Java as well with Loom, but I consider that more of a library/runtime thing than a language thing (as is the case with Loom). A hypothetical Go library could expose system threads and have an API for manipulating them, it just wouldn't be first-class in the same way goroutines are. But this is similar to how having async-await in C# doesn't negate the fact that you can use direct system threads from the standard library.
- int_19h 5y agoWith C# and .NET, you also need to consider the different kinds of pointers involved. The thing that's closest to Go's pointers would be managed references (which don't have pointer arithmetic but are GC-aware). In C#, this corresponds to in/ref/out parameters, locals, and return types. Now, the difference is that those can point directly to the stack, and thus cannot escape the function in which they were created for memory safety reasons. But conversely, they are allowed in safe code. And then there's stuff like Span, which these days lets you do something similar to Go slices: Span<byte> p = stackalloc byte[n]; (note that this is also safe code!) As for green threads, I think in case of Go they can't really be considered a library thing due to language integration of goroutines and channels. It's also not quite the same as C# async/await, because the latter isn't really a threading system so much so as syntactic sugar for CPS; it's entirely possible to have a single thread running the event loop.
- dwohnitmok 5y agorefs (and their various flavors) aren't really pointers though. They're a higher-level abstraction on top of pointers. For example, you can't have a ref of a ref, nor can you use a ref as an independent type, whereas both those things are possible in Go. refs, as their name suggests, are basically higher-level annotations to allow for pass by value or pass by reference, whereas pointers are lower-level independent types (usually in languages which only support pass by value). The point being here that references are a higher-level concept than pointers since you usually will use the latter to implement the former. > As for green threads, I think in case of Go they can't really be considered a library thing due to language integration of goroutines and channels. It's also not quite the same as C# async/await, because the latter isn't really a threading system so much so as syntactic sugar for CPS; it's entirely possible to have a single thread running the event loop. Again this isn't really a language thing anymore though, it's more of a runtime/operational detail. You could implement goroutines with CPS if you wanted and async-await could be implemented by green threads. Off the top of my head I can't think of anything in the API of goroutines or async-await that would allow you to distinguish whether they're being implemented by CPS or green threads in the background.
- int_19h 5y ago> refs (and their various flavors) aren't really pointers though. They're a higher-level abstraction on top of pointers. 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. And you can use T& as an independent type - it's not a high-level annotation on a pointer, but a fundamental CIL concept. The limitations that you describe are all about not allowing a T& that points to a local on the stack escape the function where said local is defined (whereas Go silently lifts the local to heap in this case). Hence why you can't have T&&, and why a struct that has a T& field must itself be declared as a "ref struct" (and have the same restrictions applied to it). On C# level, refs were originally a function argument modifier, as you say, solely about pass-by-value vs pass-by-reference, implemented under the hood as managed pointers. 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. > You could implement goroutines with CPS if you wanted and async-await could be implemented by green threads. Off the top of my head I can't think of anything in the API of goroutines or async-await that would allow you to distinguish whether they're being implemented by CPS or green threads in the background. 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. Async/await, though, is literally defined in terms of callbacks in the language spec; threads are completely orthogonal to all that. The dispatch loop can decide to run continuations however it sees fit, including threads - but at this point we're talking about callback-oriented code that is already desugared from "await". 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.
- dwohnitmok 5y agoI 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#.