I need to make an API that has a 'lifetime that is tied an object's existence (its time alive) but not tied to a specific borrow of the object. To illustrate:
struct Database;
struct Permission<'perm> (std::marker::PhantomData<&'perm ()>);
impl Database {
/// Acquires a permission object, doing a runtime check to ensure that's ok
fn get_perm<'perm>(&'perm self) -> Option<Permission<'perm>> {
Some(Permission(std::marker::PhantomData))
}
}
impl<'perm> Permission<'perm> {
/// Allows a borrow of the underlying data, which will be valid until the permission is dropped
fn access_data(&self) -> &'perm u64 {
&42
}
/// Does an operation that modifies the `Permission` in an inconsequential way, and does
/// not invalidate any data references
fn frob(&mut self) { }
}
fn main() {
let db = Database;
let mut perm = db.get_perm().unwrap();
let data = perm.access_data();
assert_eq!(*data, 42);
//Validate `data` is still valid after frobbing
perm.frob();
assert_eq!(*data, 42);
drop(perm);
assert_eq!(*data, 42); //Accessing `data` after perm is dropped will be UB!
}
It seems like this use case should be a straightforward thing to express with the Rust type system... But I can't seem to figure out a way to do it. I'm probably being dense... Thanks for any ideas in advance.
You understood my question correctly. I have a permission object which represents the rights (i.e. a lock guard) to access part of the database as well as cursor information. The frob methods move the cursor, but they don't change the underlying data. And as long as the lock is present, it guarantees the data won't change.
Unfortunately my simplified example didn't cover quite a bit of the complexity that makes interior mutability really hairy. I could beat this particular example into shape with interior mutability to move the cursor, but it would create other nasty problems elsewhere in the API. I can explain more but I'm worried I'll direct the discussion into the weeds if I do.
Here is a version that works but it's super un-ergonomic to use.
struct Database;
struct Permission;
struct Linchpin;
impl Database {
/// Acquires a permission object, doing a runtime check to ensure that's ok
fn get_perm(&self) -> Option<(Permission, Linchpin)> {
Some((Permission, Linchpin))
}
}
impl Permission {
/// Allows a borrow of the underlying data, which will be valid until the Linchpin is dropped
fn access_data<'perm>(&self, linchpin: &'perm Linchpin) -> &'perm u64 {
&42
}
/// Does an operation that modifies the `Permission` in an inconsequential way, and does
/// not invalidate any data references
fn frob(&mut self, linchpin: &Linchpin) { }
}
fn main() {
let db = Database;
let (mut perm, linchpin) = db.get_perm().unwrap();
let data = perm.access_data(&linchpin);
assert_eq!(*data, 42);
//Validate `data` is still valid after frobbing
perm.frob(&linchpin);
assert_eq!(*data, 42);
drop(perm);
assert_eq!(*data, 42); //Linchpin ensures `data` is still intact
drop(linchpin);
// assert_eq!(*data, 42); //Can't do this now
}
But I'm hoping there is some way to avoid two objects in the API.
The issue is that, with the method signature in the original post, nothing guarantees that the borrows from the permission get invalidated when the permission object is dropped. The permission object is effectively a ReadGuard in this case, so if that borrow can outlive the permission object, then the referenced data could be modified while the client code still has the borrow.
You want what is usually referred to as partial borrows, or disjoint borrows. Rust doesn't have a stable solution for spelling out borrows of specific fields in function signatures.
One way to mimic this would be to have a fn(&mut Database) -> (&ReadPerm, &mut FrobPerm) method signature on Database.
Another way is to disallow long-lived read permissions, and only allow them for the duration of a user-supplied closure.
Unfortunately my simplified example didn't cover quite a bit of the complexity that makes interior mutability really hairy. I could beat this particular example into shape with interior mutability to move the cursor, but it would create other nasty problems elsewhere in the API.
This is new information, what is the cursor? Is that what is mutated in frob?
If you plan to keep all database handles in singular threads, something like Cell<Cursor> or Cell<Option<Cursor>> should work without infecting the API. If it needs to be multi-threaded, then I would ask why you currently use a &mut self receiver but have no synchronization.
Yes. That's the ideal solution. Conceptually my permission object consists of two things:
1 the read guard, which the data borrows mustn't outlive
2 the cursor which must be mutable (and can't be interior-mutable, because there are other places in the API where I rely on the cursor being &self borrowed to prevent &mut self borrows from being possible)
In implementation, these two things are actually the same thing, but for this discussion I'm only focussed on exposing a sound API. So separating them into a separate "witness" perm type and "cursor" perm type (as you mentioned, or as outlined here) will be sound, it's just incredibly ugly.
I'm hoping to find the clever way to define a lifetime that doesn't outlive self, but still allows mutable borrows of self with a shorter lifetime.