4 ms·
My big complaint about Rust's borrow checking is that back references need to be handled at compile time, somehow. A common workaround is to put items in a Vec
by Animats 2y ago
My big complaint about Rust's borrow checking is that back references need to be handled at compile time, somehow.
A common workaround is to put items in a Vec and pass indices around. This doesn't fix the problem. It just escapes lifetime management. Lifetime errors then turn into index errors, referencing the wrong object. I've seen this three times in Rust graphics libraries. Using this approach means writing a reliable storage allocator to allocate array slots. Ad-hoc storage allocators are often not very good.
I'm currently fixing some indexed table code like that in a library crate. It crashes about once an hour, and has been doing that for four years now. I found the bug, and now I have to come up with a conceptually sound fix, which turns out to be a sizable job. This is Not Fun.
Another workaround is Arc<Mutex<Thing>> everywhere. This can result in deadlocks and memory leaks due to circularity. Using strong links forward and weak links back works better, but there's a lot of reference counting going on. For the non-threaded case, Rc<RefCell<Thing>>, with .borrow() and .borrow_mut(), it looks possible to do that analysis at compile time. But that would take extensions to the borrow checker. The general idea is that if the scope of .borrow() results of the same object don't nest, they're safe. This requires looking down the call chain, which is often possible to do statically. Especially if .borrow() result scopes are made as small as possible. The main objection to this is that checking may have to be done after expanding generics, which Rust does not currently do. Also, it's not clear how to extend this to the Arc multi-threaded case.
Then there are unsafe approaches. The "I'm so cool I don't have to write safe code" crowd. Their code tends to be mentioned in bug reports.
- lalaithion 2y agohttps://docs.rs/refbox/latest/refbox/ https://docs.rs/refbox/latest/refbox/
- Animats 2y agoNeat. It's still run-time checking. A good idea, though. The one-owner, N users case is common. The trick is checking that the users don't outlive the owner.
- recursivecaveat 2y agoYes the "fake pointer" pattern is a key survival strategy. Another one I use often is the command pattern. You borrow a struct to grab some piece of data, based on it you want to modify some other piece of the struct, but you can't because you have that first immutable borrow still. So you return a command object that expresses the mutation you want, back up the call stack until you're free to acquire a mutable reference and execute the mutation as the command instructs. Very verbose to use frequently, but often good for overall structure for key elements.
- Animats 2y agoYes. Workarounds in this area exist, but they are all major headaches.
- saurik 2y ago> A common workaround is to put items in a Vec and pass indices around. This doesn't fix the problem. It just escapes lifetime management. Lifetime errors then turn into index errors, referencing the wrong object. That people seriously are doing this is so depressing... if you build what amounts to a VM inside of a safe language so you can do unsafe things, you have at best undermined the point of the safe language and at worse disproved the safe language is sufficient.
- Animats 2y agoThat's a good way to put it. I'll keep that in mind when trying to convince the Rust devs.
- edflsafoiewq 2y agoThis is a common pattern everywhere, not just in Rust. Indices, unlike pointers to elements, survive a vector reallocation or serialization to disk. IDs are used to reference items in an SQL database, etc.
- imtringued 2y agoI hope you realize that index buffers are an industry standard in graphics APIs.
- Animats 2y agoVertex index buffers are not usually updated dynamically. Vulkan bindless descriptor tables are updated dynamically, and do have pointers to memory addresses. Those require a slot allocator and a deferred, interlocked deletion system to prevent deleting something from underneath a renderer. Not fun. I've been working on that.
- saurik 2y agoI do. I am not saying they are not useful... I am saying they are not safe. In a world where you care about the safety guarantees of Rust, you'd take an API that insists on manual index management and you would build an abstraction over it that allowed the compiler to prove its correctness using a proof system of some form, maybe borrow checking, or maybe something much more sophisticated.