Here's an approach to back references which mostly works, but I'm not quite there yet.
The concept is single ownership using Rc<RefCell> with restrictions. That's encapsulated in a type Owner. You can't clone Owner, but you can get a weak pointer from the Rc. Then you can upgrade and borrow to use the value.
The new thing here is that, when an Owner is dropped, there's a check that there are no remaining weak or strong references. Those are not allowed to outlive the Owner. Thus, upgrade cannot fail. Of course, borrow can still fail. The idea is to use small non-overlapping borrow scopes so that borrow clashes are eliminated.
This creates an ergonomics problem and an overhead problem. First step is to fix the ergonomics. Currently, I have to write
w_weak.cell.upgrade().unwrap().borrow_mut().x = 100.0;
let xval1 = w_weak.cell.upgrade().unwrap().borrow().x;
I'd like to be able to write
w_weak.borrow_mut().x = 100.0;
let xval2 = w_weak.borrow().x;
which is less of a pain.
But the implementation of WeakOwner, which is currently commented out, returns a temporary. Remove the "/*" comment markers and try to compile. I don't have a workaround for that. That's what I'm looking for.
Conceptually, you need to combine the Rc and the Ref into one thing without the borrow that Ref has. This is why many lock libraries have “owned guards”:
In order to build one of these out of specificallyRc and RefCell, you need either a self-referential-struct library (yoke, ouroboros, etc) to combine them, equivalent unsafe code of your own, or to write your own version of RefCell with an owned Ref.
If you don’t mind using Arc (it's hardly any more expensive) you can use lock_api with refcell-lock-api instead of writing it from scratch; this has the advantage of not involving any unsafe code.
I'd kind of figured that. Since Rc does this, Owner could potentially do it too. But I'd have to write something tricky and unsafe.
Didn't know about refcell_lock_api - Rust which is worth a try for this proof of concept version.
There's also the possiblity of doing this with a macro, expanding w_weak.borrow_mut().x = 100.0;
into w_weak.cell.upgrade().unwrap().borrow_mut().x = 100.0;
but using a macro would probably mean writing borrow_mut!(w_weak).x = 100.0;
which is ugly.
I'm trying to do this without being too clever, because I have to convince people it's safe.
The goal is to have something C programmers, and C to Rust translators, can use when converting code. This Owner type has roughly the semantics of C pointers, where backpointers are common, but is safe Rust.
I doubt this works for you but I'll mention it just in case: rather than storing a back pointer, you may be able to store the id/index/key of the owner and get a pointer to it at the time you need access. This depends on having the owners in a collection of some kind, and on having access to that collection when you need it.
As @jonh already pointed out, it looks to me, too, that you're not solving the right problem:
According to your textual description, your requirement is that a specific instance of a ref-counted reference gets dropped as the last one. So you only need to do this:
// Deliberately not Clone
struct TheLastOwner<T>(Rc<RefCell<T>>);
impl<T> Drop for TheLastOwner<T> { ... }
impl<T> std::ops::Deref for TheLastOwner<T> { ... } // &Rc<...>
In all other places, you can simply pass around naked Rc or Weak references as many as you'd like. When TheLastOwner gets dropped, the invariant is checked, matching your textual description.
So maybe you're trying to achieve something not described here, maybe your description is missing something, or maybe it's worth revisiting your requirements more carefully.
I'd like to make that more concise. Then more efficient, so that users could freely use narrowly scoped borrows. If all borrow scopes are disjoint, no borrow will fail. Narrow borrow scopes help with checking that.
Maybe you still find that uncomfortable. It's generally a bad idea for many reasons to make language A look and behave like language B, but here are some dirty macros to the rescue:
I would really not recommend simulating a different language, in whatever language you're working in. I did this far too often in my career, and in the long run, it always ended in some sort of disaster.