.borrow() already has a hidden unwrap. There's try-borrow(), but the assumption for .borrow() is that it will always succeed.
What I'd like to do is move as much of the checking as possible to the drop() of the owning object, and possibly to compile time. If .borrow() calls are very local, it's not too hard to determine that the lifetimes of the borrowed objects don't overlap.
Upgrade is easy to check cheaply at run time for Rc-type cells. Upgrade only fails if the owning object has been dropped. At drop, if weak_count == 0, no dangling weak references outlive the object. If there are more strong references, drop would not be called. With that check, .upgrade() will never fail.
After all, when a programmer codes a potentially fatal .borrow(), they presumably have some reason to be confident the panic won't trigger.
`.borrow()` is also a function call and thus it's not totally unexpected that it may panic.
What you're propositing however is to have the *`.name` syntax* do a lot of work and even potentially panic. That's the controversial part.
> If .borrow() calls are very local, it's not too hard to determine that the lifetimes of the borrowed objects don't overlap.
It's not too hard when you see the `.borrow()` and `.borrow_mut()`, but it becomes much harder when they become invisible and you have to check all `.field` accesses for this.
> Upgrade only fails if the owning object has been dropped. At drop, if weak_count == 0, no dangling weak references outlive the object. If there are more strong references, drop would not be called. With that check, .upgrade() will never fail.
I'm not sure what's your point here. `.upgrade()` will fail if all the owning `Rc`s have been dropped, and that's enough for this to be problematic.
> `.upgrade()` will fail if all the owning `Rc`s have been dropped, and that's enough for this to be problematic.
Want to catch that where the Rc is dropped, not at .upgrade time(). And maybe at compile time eventually.
> It's not too hard when you see the `.borrow()` and `.borrow_mut()`, but it becomes much harder when they become invisible and you have to check all `.field` accesses for this.
Good point. This is checkable at run time, but the error messages will be terrible,
because you don't know who's got the conflicting borrow. You only know it exists.
It's very similar to finding double-lock mutex deadlocks, though. Post-mortem analyzers can help. Maybe in debug mode, save enough into to produce messages such as "PANIC - double borrow at foo.rs, line 121. Conflicting borrow of the same item was at bar.rs, line 212." That's useful for locks, too. Problems of this class are all of the form "this thing here conflicts with that thing way over there", and users need to know both ends of the conflict. The Rust borrow checker is good about that.
This works much better if we have borrow lifetime checking at compile time. I've written a bit about that, and need to overhaul what I've written.
> Want to catch that where the Rc is dropped, not at .upgrade time(). And maybe at compile time eventually.
Catching that at compile time will require something that looks more like a reference into the `Rc` than a `Weak`. Most likely not the ergonomics that you want.
Catching at runtime is IMO not much different than panicking on the field access and IMO it's not enough.
> This is checkable at run time
Not sure how you check that a panic cannot occur at runtime without panicking in the first place...
> It's very similar to finding double-lock mutex deadlocks, though.
I'm not sure how they are similar. Last time I checked mutexes still required an explicit `.lock()` to be locked.
> Problems of this class are all of the form "this thing here conflicts with that thing way over there", and users need to know both ends of the conflict. The Rust borrow checker is good about that.
The last sentence doesn't make sense. You wanted to use `Rc`, `Weak` and `RefCell` precisely to avoid the kind of restrictions that are needed for the borrow checker to perform its analysis. Either you keep those restrictions and you have reference semantics or you discard them and are forced to perform runtime checks. There's no free lunch.
Your arguments basically boil down to hypothetical compile time check that doesn't exist or to arguing that hidden runtime checks are fine because you can test the code in debug mode to hopefully catch them failing. Neither of them are convincing enough.
What I'd like to do is move as much of the checking as possible to the drop() of the owning object, and possibly to compile time. If .borrow() calls are very local, it's not too hard to determine that the lifetimes of the borrowed objects don't overlap.
Upgrade is easy to check cheaply at run time for Rc-type cells. Upgrade only fails if the owning object has been dropped. At drop, if weak_count == 0, no dangling weak references outlive the object. If there are more strong references, drop would not be called. With that check, .upgrade() will never fail.
After all, when a programmer codes a potentially fatal .borrow(), they presumably have some reason to be confident the panic won't trigger.