I’m curious about the rooting strategy, which seems to be the breakthrough here, and the ergonomic hit you mentioned it introducing. It seems like whenever you need a root there’s only really one way to do it. In other words, it’s 100% rote boilerplate and there’s no decision to make, you just add in letroot!() as appropriate. Is that correct?
I really don’t know much about Rust macros… but would this allow a hypothetical #[gc] attribute on a function that eases the ergonomic hit? Something like the following, where each root!() would get transformed into an appropriate letroot!() call that appears in the right place before the statement, with an appropriately unique identifier.
let x: Gc<u8> = root!().gc(0);
fn_call(1, 2, root!().gc(0), root!().gc(1));
let y: Gc<Foo> = Foo::new(root!());
becoming:
letroot!(root);
let x: Gc<u8> = root.gc(0);
letroot!(root1); letroot!(root2);
fn_call(1, 2, root1.gc(0), root2.gc(1));
letroot!(root);
let y: Gc<Foo> = Foo::new(root);
As far as I understand, you can’t define a real macro like I have the root!() calls in the original, since it needs to do things out of order. But I think an attribute macro could do that rewrite inside its function.
I’m probably getting a little ahead of myself here… 
Edit: I guess what I’m really looking to confirm here is that the rooting strategy you have seems to require very little magic to do automatically. That makes me hopeful about future cleverness to ease any ergonomic hits.