Borrow_channel, an abstraction for runtime checked borrowing

docs
repo

I ran into an issue where I wanted a closure to borrow some data, but could not pass a reference, as the invocation of the function happens in code I do not control. To solve this, I came up with an abstraction that allows sending a borrow from one code site to another. If the providing borrow ends before the using borrow, the program is aborted.

There are a few ways to think about this:

  • A channel, through which you send a borrow.
  • A RefCell, where data is not owned by the cell, but only lent to it.
  • A version of scoped threads, where instead of creating a new thread, the function using the borrowed data runs on the same thread.

As there is some unsafe code in here, I would really appreciate it if you could take some time to look over it. I am particularly uncertain about the soundness of Deref for BorrowChannelGuard.

Well… lifetimes are difficult, aren’t they? :innocent:

use borrow_channel::BorrowChannel;

fn main() {
    let c = BorrowChannel::<&String, _>::new_sync();
    let s = String::from("Hi there, everyone!");
    let mut r: &str = "";
    c.lend(&s, || {
        r = *c.borrow();
    });
    println!("{r}");
    drop(s);
    println!("{r}");
}
Hi there, everyone!
�Ϳ��WWt�_xne!
8 Likes

I was worried about Deref being broken. Thank you for finding an example! I'll try to see if I can break with in the same way.

And it looks like there is not really a way to have Deref, as Deref::Target does not depend on the lifetime of the self borrow used in Deref::deref :slightly_frowning_face:.

I think with seems fine. I wonder what the best analogous pattern would be for an immutable-access version of it :thinking: – in the general setting of your Reborrowable, e.g. also assuming user-types to be supported…

maybe sth like this?

pub fn with_ref<'b>(&'b self, f: impl FnOnce(&'b T::Borrowed<'b>))

On that note, given there’s no HRTB-requirement on the closure anyway, I suppose you could (more conveniently) just return the re-borrow (for with; or borrow-of-reborrow for with_ref) instead of requiring a closure?


As for Deref, you could consider offering it in case of for<'l> T::Borrowed<'l>: Deref (and returning a reference to the Target of that), as a conveninence thing… i.e. in that case, it’d probably be a good idea to implement it in terms of a more general API (such as a with_ref method). I suppose, that’d even allow a DerefMut (implemented in terms of with)?

Thanks for the input!

Yes, with taking a closure is a relic of an earlier design. I hadn't noticed it has become unnecessary. I will probably replace it with something like this:

pub fn get_mut(&mut self) ->T::Borrowed<'_> {
      unsafe {
          let ptr: *const MaybeUninit<T::Borrowed<'static>> = self.channel.data.get();
          ptr.cast::<T::Borrowed<'_>>().read()
      }
  }

The immutable version would only be sound for types with IS_SHARED==true. I think the best way to encode that would be associated const equality, but that does not seem anywhere close to ready. Could be worked around relatively easily by introducing something like unsafe trait SharedReborrowable.

I'll have to play around with Deref a bit to get a feel for it, your proposal sounds very interesting.

I might of course be missing something; but I thought/hoped that returning &'b T::Borrowed<'b>, which for mutable references would be &'b &'b mut Target, should perhaps be fine for all types… (at most perhaps requiring some additional documentation on the Reborrowable trait)

Oh, yes. With returning &T::Borrowed that does sound plausible at least.