4 ms·
It's worth noting that Go's value types are not actually memory safe, however, since it allows nontrivial concurrent mutations to shared multiword objects (as d
by Jweb_Guru 8y ago
It's worth noting that Go's value types are not actually memory safe, however, since it allows nontrivial concurrent mutations to shared multiword objects (as described in https://blog.stalkr.net/2015/04/golang-data-races-to-break-memory-safety.html https://blog.stalkr.net/2015/04/golang-data-races-to-break-m...). Unless that got fixed recently somehow?
I believe in most of the other languages you mention, value types are restricted in various ways to avoid that problem (for instance, mutations are only allowed to value types on the stack that don't escape, or multiword value types can only be updated in ways that preserve data structure validity even if updates "tear", or multiword value types are immutable).
- pjmlp 8y agoWith the exception of Go and C#, there are no limitation on value types. All those languages are GC enabled systems programming languages. Regarding Go the memory safety in multithreaded code is orthogonal to having support for value types or not.
- Tarean 8y agoIf the manually allocated memory may contain references to managed objects then it still needs to be traced by the gc. That or you start pinning managed objects or pull everything related into manual world. Both kinda suck.
- pjmlp 8y agoManually allocated memory in some of those languages is only allowed in the context of unsafe packages. In any case, the point is that you should only do that for the code paths that actually matter, after profiling the application and not everywhere. Be productive, make use of the GC, naturally taking into the consideration the algorithms + data structures being used. If that is still not enough to meet the application's SLAs, then manually allocation comes into play.
- Jweb_Guru 8y agoIt's not orthogonal at all. Nontrivial mutations to multi-word sized values are inherently racy operations that can lead to UB if you don't have restrictions on how value types can be used. D isn't memory safe; Active Oberon employs mandatory object locking on writes (which is a rather heavy runtime solution); Cedar values are either immutable, or always valid with respect to word sized mutations (as far as I can tell, unions, for example, can only exist behind a pointer indirection). Modula 3 objects are not, as far as I can tell, value types (i.e. they must be heap-allocated and mutation creates a new record), and those are the only types it has where a "tearing" mutation could invalidate the runtime representation. So, I disagree: all of the languages you mentioned either are memory unsafe when you use multiword value types concurrently, have one of the restrictions I mentioned, or (in the case of Active Oberon) conservatively lock on all writes. These value types are not "unrestricted" compared to primitives, all of which can be mutated in-place without allocating or locking, or to heap objects, which can be updated atomically even for interesting values at the cost of an allocation. Moreover, the issue is intimately tied to GC, or more accurately to the programmer not having to explicitly keep track of whether objects are uniquely owned or not (which is generally the function of a GC). As a result, if garbage collected objects can hold references to nontrivial value types, there is almost no way to avoid this problem.
- pjmlp 8y agoI don't get why you are jumping into memory safety in multi-threaded environments. We are discussing GC enabled systems programming languages with support for unsafe operations, not Rust's idea of thread safety. D is memory safe in the context of @safe code, of course @system code is unsafe. Active Oberon and Modula-3 also have record types, strings and arrays that can be stack allocated, declared on the global data memory segment or just manually allocated in unsafe packages. In both cases I can also declare untraced pointers for memory blocks in unsafe packages, that the GC will gladly ignore.
- Jweb_Guru 8y agoLack of thread safety leads to undefined behavior when you have nontrivial mutations to value types with tearing. That has nothing to do with some abstract notion of thread safety, nor does it have anything to do with Rust. It's a very straightforward consequence of the failure of (for example) things like bounds checking, or interpreting an integer as a pointer. Record types with fields that don't depend on one another, strings and (fixed-size) arrays only support "uninteresting" mutations that remain valid even in the presence of atomic tearing. They are restricted compared to the general value types that you can have in a language like C++ (for example, sum types, unions, "fat objects" like Go's interfaces, or vectors with dynamic lengths), because for the latter having a write tear can lead to undefined behavior (for instance, overwriting a value vector can lead to the pointed-to vector part temporarily having a different length than the length field would indicate, which can easily lead to a buffer overflow). The memory safe fragment of D has similar restrictions on its value types to the above. For example, its value arrays must have a size known at compile time. Of course you can support such features in the unsafe fragment of a language. However, this is a pretty significant deterrent to actually using the feature and idiomatic code will avoid it most of the time.