Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

TL;DR: Don't use unsafe to break the rules. Use unsafe to enforce the rules in new ways.

Great journey. That's the thing about "unsafe" and "questionable parts"... if they really are questionable, then don't do 'em. In this case, if it is the case that holding a reference to a GC object guarantees that that object can't be freed, then it's not questionable. Proving that to be case can be fun tho.

The question is: does my unsafe code allow violating the borrow rules?

Cell, RefCell, RwLock etc give you interior mutability, but there is no way to violate the borrow rules with them. On the surface, it looks like you are violating them because "I have a non-mut thing that I can mutate".

If you build a thing that allows two distinct &mut T's to the same thing, then you are basically asking for a bug. Don't use unsafe to break the rules. Use unsafe to enforce the rules in new ways.



If someone else depends on this project, I'd definitely not implement the questionable stuff. You're right that proving whether it's safe can be lots of fun, and I'm planning to try it out.

Based on what I've read, it's entirely possible. If my Rust knowledge is correct, there are 2 things that have to be proven - the object's lifetime and the borrow rules.

Proving the lifetime can be done at compile-time based on what was discussed in these articles https://without.boats/tags/shifgrethor/. But that still leaves us with proving that we don't violate the borrow rules. AFAIK, the only way to do it in my implementation is with Cell and RefCell enforcing the borrow rules at runtime.

Before removing all the safety checks, I actually used Gc<RefCell<T>> for mutable objects, so the borrow rules were enforced but not the lifetime. However, I decided to remove RefCell<T> to have some fun and see how I could shoot myself in the foot.


You don't want to use `RefCell` for something like this- it is still too strict for Lox's semantics.

Doing this properly means putting `GcData`'s fields (and/or `ObjFun`/`ObjClosure`/etc's fields) in plain `Cell`s, and using that for all mutation without ever forming any `&mut` references.


Can you elaborate on why it's too strict? I got RefCell working and passed to test suite just fine. Maybe the suite don't take into account some that's specific to Rust.

If I remember correctly, there were some panics cause by RefCell borrows. But those got sorted out once I handled borrowing in the correct order.


I guess a better way to put it is, if you got Lox working with RefCells, then you didn't need RefCells (or their overhead) to begin with - you must not have been holding those dynamic borrows any longer than a single VM instruction, or something close to that.


Yes, the borrows were only held for roughly a single instruction. But I don't see how you can mutate the Table/HashMap of fields in an object with only Cell tho


Technically it is possible to swap the HashMap out of the Cell, mutate it, and put it back, but that is maybe even worse than the RefCell.

What I would expect a production language implementation to do is build its own table that integrates its solution for interior mutability into its design. There are several other things (e.g. iterators, concurrent GC) that will also contribute to the need for a custom table, so (again for a production language) this is not too crazy.


Ah! I could imagine a table backed by an array of Cell since all the possible values in that table implement Copy. There is probably a better way for the table to have interior immutability. But I think I get what you were saying.


Just for reference, the gc-arena crate (https://github.com/kyren/gc-arena) we use in Ruffle's Actionscript 1/2/3 interpreters and across the entire engine, does this. As far as we know, it's safe and sound, at the cost of some limitations, mainly: to compile-time prevent sweeping while holding onto Gc references, every struct and function touching Gc objects passes an extra 'gc lifetime. And not being able to run the GC at arbitrary time can be major issue depending on what you're doing; in Ruffle we incrementally collect between frames and pray that no single frame will ever allocate enough to OOM in the middle of it :)

And yeah, RefCells everywhere.


Thanks a lot for the insight. I think if I do it the way shifgrethor did, I can have GC at arbitrary time. But I'm not so sure about that.

With the way Rust may perform optimizations on the assumption of no alias, I guess there's currently no way to avoid RefCell if we want to be 100% on the safe side.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: