There are also escape hatches, between unsafe leaking and the RefCell type, to provide “interior mutability”, i.e. mutable aliasing via an without the memory safety issues which come with this. Note I have only had to use the unsafe bits when passing memory up to a c stack.
I don’t believe there is a tracing GC implementation in the standard library, although I’ve heard plenty of discussion and you can find some simple implementations on crates.io. However, I would assume there would need to be some language changes to properly account for assumptions on how the Drop (free) functionality fires, not to mention significant backend work for tracing to work.
If you allocate something new, Rust pretends it is independent, assuming that malloc won't muck with the other objects it knows about.
What if we relax that assumption? Allocations may perturb other allocations - that's how moving GC heaps work! Can that be modeled usefully in Rust?
Rust has the capability to do things like execute load barriers on access, so it could still work when objects have been moved around.
&mut T means that there is only one pointer (the &mut T) that will be used to access some memory. You can have as many other pointers to that memory as you want, as long as you don't invalidate the first one.
This code is safe:
let mut foo = 13_i32;
let x: &mut i32 = &mut foo; // First &mut
{
let y: &mut i32 = unsafe { std::mem::transmute(x as *mut i32) }; // Second &mut T (copy)
*y = 42; // Write through second
}
dbg!(*x); // Read through first
You can run this under miri here: https://play.rust-lang.org/?version=stable&mode=release&edit...Without any more specific knowledge, I bet the way a generational garbage collector moves data around really messes with Rust's view of the world...
Getting shared mutability as a user of GC is basically the same as getting shared mutability in any other scenario in Rust. You use `UnsafeCell<T>` or a safe wrapper around it, like `Mutex` or `Cell`. You can get a feel for this even in idiomatic Rust using `Rc` instead of a tracing GC.
When you start looking at other methods of GC things get trickier. Even with `UnsafeCell<T>` the GC can't, in general, know whether something is borrowed (mutably or immutably) by the mutator, so the problem to be solved is finding ways to enforce that there are no references into the heap at all, when collection needs to happen.
But when you think about it, this is actually not so different from the usual problem of GC safepoints. The problem is the same- enforce that there are no un-rooted pointers into the heap, when collection needs to happen.
And it turns out there are some tricks you can do with lifetimes to get borrowck to enforce safepoints for you, and it could even be made pretty ergonomic in the future.
Some early discussion of the problem space:
* http://blog.pnkfx.org/blog/categories/gc/
* https://manishearth.github.io/blog/2015/09/01/designing-a-gc...
* https://manishearth.github.io/blog/2016/08/18/gc-support-in-...
A design from Servo, and a paper to go with it: https://github.com/asajeffrey/josephine
A series of blogposts describing a more recent attempt: https://boats.gitlab.io/blog/post/shifgrethor-i/
The most recent attempt I know of: https://github.com/kyren/gc-arena/
I think gc-arena is fairly similar to josephine- both treat the GC heap itself as a container-like object, requiring `&` or `&mut` access to dereference a GC pointer. Gc-arena also suggests that we could use generator yield points as GC safepoints- they have exactly the required lifetime properties. So you might (to borrow Python syntax) `yield from fn_that_allocates_gc_memory()`, with the compiler ensuring you don't hold any references into the heap across that call.
Both gc-arena and josephine appear to take the "unidirectional" approach, where there is no recursive re-entrance into the GC heap. For example gc-arena will not collect while the heap is being mutated, which is Rust-friendly but also impractical.
GC'd languages typically require reentrancy into managed code (managed -> native -> managed), and this has historically been the source of type confusion and other security vulnerabilities.
This is where the generator stuff I mentioned comes in- I think the approach is much more practical than it seems.
At the end of the day, any GC language needs to ensure there are no live un-rooted GC pointers at points where collection takes place. So any realistic Rust/GC integration will need to solve the same problem, if it is to retain memory safety.
And the best way (so far) to ensure all references into the heap have disappeared is to make sure they are all derived from a function parameter with an unconstrained lifetime, because then the mutator knows nothing except that it might go away when it returns. (This is "generative lifetimes" from the links.)
The real trick then is to make it look like the mutator is returning, without actually forcing it to exit. A yield point in a generator can do this, even without ever actually yielding. So you can manually insert collection points in the middle of a Rust mutator, and you can have a "managed -> native -> managed" stack where the native frame thinks its managed callee can yield across it, and borrowck will ensure anything derived from that reference parameter is gone.
Of course this is a long way off from Rust today, where generators aren't available on stable (though perhaps you could hack something together with async/await), but does look like it should eventually be possible to have reentrant heap access and memory safety across Rust and a GC language.
For example, assume v8-ish semantics:
let mut cell = allocate(0.0);
let counter : &mut f64 = cell.as_mut_ref();
*counter += 1;
stuff();
*counter -= 1;
Here `stuff()` may trigger further allocations which result in moving `cell`, leaving `counter` dangling.