5 ms·
Thanks! There are three things preventing someone from using the object after destroying it: * If it was an owning reference, the compiler will ensure nobo
by verdagon 6y ago
Thanks!
There are three things preventing someone from using the object after destroying it:
* If it was an owning reference, the compiler will ensure nobody else uses it.
* The program will halt if there are any live constraint references to an object we destroy, preventing anyone using one of those.
* If it was a weak reference, the language will (conceptually) null that out when we destroy the object.
Locking goes deep into Vale's concurrency story, but I can try to sum it up:
* By default, an object can't be visible to two threads, every thread has its own isolated region (similar to Pony and Rust).
* We can use a mutex to contain a region, and Vale's "region borrow checking" can make sure references don't escape the lock's duration.
I'm curious about that second safety hole Rust has. I don't think Vale has that same hole, as we initialize every array with a size and a lambda, and there's no opportunity to stop it halfway through (well, our form of "exceptions", involving pure functions or rollbacks, could stop it and keep everything safe), you can see an example in our little roguelike sample [1] on line 415.
[1]: roguelike.vale, https://github.com/ValeLang/Vale/blob/9541d414ce6b1c2745de19f1335e4f213cae32cf/Midas/test/tests/roguelike.vale#L415 https://github.com/ValeLang/Vale/blob/9541d414ce6b1c2745de19...
- rayiner 6y agoThis is really a very cool language! I wonder how nested regions works out in practice. That seems like an obvious way to deal with Rust’s limitations on aliasing (IIRC Cyclone used regions that way) but I wonder what the pitfalls are in actual use.
- verdagon 6y agoThanks! We're really excited to see how they work out too, there's some exciting potential in there. I can imagine having programs which are tree-like hierarchies of regions, but I'm starting to suspect we can have directed acyclic graphs, and perhaps even very small cycles in them...
- Animats 6y agoI'm curious about that second safety hole Rust has. All variables are supposed to be initialized before use. But a Rust Vec, like a C++ std::vector<T>, has both an in-use "size" and a usually-larger pre-allocated "capacity". This allows adding elements without getting a new memory block. Implementing Vec safely in Rust runs into the problem of initializing the capacity not yet used. Not initializing it is unsafe, so Vec has to have "unsafe" code. I'd argue for this: First, data types should be divided into "fully mapped" and "not fully mapped". A fully mapped type is one where all bit combinations are valid. Fully mapped types don't have to be initialized to be memory-safe. This includes integers, floating point on most systems, but not booleans. (C++ calls this "plain old data", or POD, but allows Booleans and enums. Whether Booleans with values other than 0 or 1, or out of range enums, should be considered "plain old data" is a good question for a language designer.) If it's not fully mapped (worst case, it has a pointer or reference in it), what do you do about uninitialized data in the unused "capacity" area? If it's not initialized, you have a pointer with a junk value, as far as the language is concerned. If it has to be initialized, you have a problem if initialization requires an input value. So I'd argue for the concept of a partially initialized array as a language feature. For simplicity, only being initialized from the beginning to a limit (0..N) should be supported. (Yes, there is such a thing as a giant sparsely initialized hash table where allocation info is stored outside the table. See Google's sparse hash table. That's very rare.) The idea is that you designate some field of the structure as the "size", and accessing beyond "size-1" is a subscript error. But you can initialize at "size", and this increases "size" by 1. You can decrease "size" if you own the whole object. If you have both of those features in a Rust-like language, you should not need "unsafe". Except to call other languages, maybe.
- verdagon 6y agoThat's interesting! I'm fairly certain Vale avoids the problem with its lambda approach, but I can see a partially initialized array being very useful for Rust. In fact, I think Cone [1] does exactly that; you can designate a certain field to be the size for another an array field. I haven't looked at google's sparse hash table yet, I'll have to codesearch that =) [1] https://cone.jondgoodwin.com/ https://cone.jondgoodwin.com/
- Animats 6y agoIn fact, I think Cone does exactly that; Interesting. Cone has every known approach to memory management. GC, refcounts, arenas, pools, single ownership, thread local... Worth an experiment, but you probably don't want to go there for a widely used language. Think of the library compatibility problems. For locking purposes, there's an argument for thread-local, shared, and transferable objects. Transferable objects are what you put in a queue between two threads. They're single-thread and don't need locking when in use, but can be handed off to another thread. It's useful if transferable objects are transferable by copy. This is useful for machines where not all memory is shared. We're headed there; cache management gets harder and harder as the number of cores starts to approach 3 digits. So think ahead here.
- algesten 6y agoI don't think you can have a "fully mapped type" the way you describe it. Uninitalized memory in today's compilers do not have an observable value. https://www.ralfj.de/blog/2019/07/14/uninit.html https://www.ralfj.de/blog/2019/07/14/uninit.html
- twoodfin 6y agoThe compiler’s representation of an uninitialized lvalue might not be observable as the article describes, but an uninitialized heap allocation from the OS (i.e. malloc()) is observable by design. Of course a language and its clever implementation can make reading unintialized values from malloc()‘d objects UB with all that implies, but that’s a spec choice. So I believe you could define what’s returned by malloc() as safely castable to one or more “fully mapped types”, which would solve the problem for vectors at least of those types.