3 ms·
Interesting work! When I read it, I came away with the impression that vale provides guarantees like Rust, except that you get runtime errors rather than compil
by drewm1980 6y ago
Interesting work! When I read it, I came away with the impression that vale provides guarantees like Rust, except that you get runtime errors rather than compile time errors, and that the guarantees for thread safety are weaker.
It's likely these are well motivated design decisions since you designed this with knowledge of Rust. As a programmer relieved by the transition from C++ to Rust, going back "halfway" in the direction of C++ will be a hard sell, even if it makes some things easier.
- verdagon 6y agoAbsolutely. It's a hard sell because popular opinion is that Rust gives you compile-time safety with zero cost, and showing people that that's not the case will be difficult. There are three things that we'll have to show the world: 1. While Rust's references are safe and efficient, they force other would-be-references into less efficient patterns (the afterword dives into this a bit). 2. The article didn't mention this, but Rust's approach has a lot of run-time checking too. Every expect/unwrap and every Rc<RefCell<T>>'s borrow/mut() is an assertion, just like Vale's constraint refs. 3. These run-time "halts" won't actually be panics. In debug mode, it can pause and let you continue after a key-press (and even whitelist on a class-by-class basis). Then, once done running the program, one can easily convert that constraint ref to a weak ref in code if desired. Also, the guarantees for thread safety should be the same as Rust, could you elaborate on that? Anyway, even if the above three reasons aren't enough, Vale's ease-of-use should be more than worth it, for those who find Rust's approach too restrictive. It'll be challenging to show readers the broader picture, and that Rust and Vale are two equivalent approaches to safety, but we're up to the challenge =)
- dan00 6y ago> 2. The article didn't mention this, but Rust's approach has a lot of run-time checking too. Every expect/unwrap and every Rc<RefCell<T>>'s borrow/mut() is an assertion, just like Vale's constraint refs. I would say, that if you’ve a lot of RefCells in your code, then you most likely doing it wrong. Having just a few RefCells for higher level, bigger structures seems to make more sense. If you get these structures from a RefCell all the further access on them can be statically verified during compile time. It’s not only about the performance but about the correctness of your code, how easy you can reason about it. The more dynamic checks you have, the less you can be sure about your code.
- verdagon 6y agoThe other main (safe) alternative to Rc<RefCell<T>> for mutable aliasing is Vec + generational indices, which also involves an expect/unwrap assertion whenever you expect something to be alive and it isn't. If you need mutable aliasing, you're going to incur run-time costs, whether in Rust or any other language. The other approach that I like for mitigating the cost of mutable aliasing (in Rust and Vale and some other languages) is to rearchitect the program such that the world (or some subset of it) is frozen, we do a lot of heavy calculation and make an "effect", and then unfreeze the world to apply it. Vale's region borrow checker [1] will make that zero-cost, like Rust's borrow checker does. If we must have mutable aliasing, I like the balance you speak of, where one can use a blend of runtime-checked aliasing at the high level and borrow checking at the low level. I hope that borrow checker zen moves in that direction. Cone [2] emphasizes that exact balance, and I think there's a lot of promise there. [1] https://vale.dev/ref/regions https://vale.dev/ref/regions [2] http://cone.jondgoodwin.com/ http://cone.jondgoodwin.com/
- ridiculous_fish 6y agoNot the author, and still new to Rust, but I think this approach is interesting in Rust as well. Consider visiting a tree: enum Tree { Branch(Box<Tree>, Box<Tree>), // left, right Leaf(i32) } fn postorder_visit(t: &mut Tree, f: fn(&mut Tree)) { // ??? } A recursive implementation of `postorder_visit` is trivial but risks stack overflow. A heap-allocated stack (say, `Vec<&mut Tree>`) runs into problems with the borrow checker, which knows about the C stack, but not your stack. So an overflow-safe implementation must use unsafe, but still wants to have confidence in ordered destruction. You could make it a `Vec<*mut Tree>`, but now there are zero checks! The idea here is, in debug mode only, replace `Box` with `Rc` that checks its refcount upon drop, and `&mut Tree` with Rc as well. Now your debug mode does not require unsafe, and your release mode is fast. You have dynamically enforced ownership in a regime inaccessible to the borrow checker.
- btschaegg 6y agoSince I'm still starting out with Rust, there's probably just something I'm missing here, but couldn't you easily solve that with `Cell<T>` or `RefCell<T>`?
- ridiculous_fish 6y agoI hope to see an expert answer to this question. In my lame attempt, Cell was too awkward and RefCell was also awkward with a side of runtime cost.
- deleted 6y ago[deleted]