Making a RefCell a generic function argument

Hello,

I am currently working on a library, that allows you to create a “SubCursor” from structs, that implement Read, Write or Seek. It makes it possible to have a Reader, that only has access to parts of the Cursor.
Most of the coding is already done and the next step is to make it conform to the Api-Guidelines before I start documenting the codebase.

The core concept is, that the user passes a reference of a RefCell,

struct SubCursor<C> {
    cursor: &'s RefCell<C>
}

impl<'s, C> SubCursor<'s, C> {
    pub fn new(cursor: &'s RefCell<C>) {
        Self {
            cursor: cursor,
        }
    }
}

that wraps around the Cursor to the struct and I want to make it generic, because there exist multiple different implementations of RefCells like PinCell, AtomicRefCell or QCell and I only care about the .borrow_mut() function.

I searched a bit in the Std-Library and found the BorrowMut trait and thought, I found a solution for the problem, but it seems to be for such cases:

fn sample(value: &mut String);
fn sample<T: BorrowMut>(value: D);

Test Code
I wrote some test code that covers exactly, what I want to do:

use std::io::prelude::*;
use std::borrow::BorrowMut;
use std::cell::RefCell;
use std::io::Cursor;

type Result<T> = std::result::Result<T, Box<std::error::Error>>;

fn generic_function<'s, C, T>(value: &'s T) -> Result<Vec<u8>>
where
    C: Read + Seek,
    T: BorrowMut<C>
{
    let mut result = [0; 4];
    value.borrow_mut().read(&mut result)?;
    Ok(result.to_vec())
}

fn specific_function<'s, C>(value: &'s RefCell<C>) -> Result<Vec<u8>>
where
    C: Read + Seek,
{
    let mut result = [0; 4];
    value.borrow_mut().read(&mut result)?;
    Ok(result.to_vec())
}


fn main() {
    let data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    let cursor = RefCell::new(Cursor::new(data));

    let result = specific_function(&cursor);
    // Ok([0, 1, 2, 3])
    println!("{:?}", result);
}

The “specific_function” works as expected, but the “generic_function” gives a compiler error.

error[E0599]: no method named `read` found for type `&mut &'s T` in the current scope
  --> src/main.rs:14:24
   |
14 |     value.borrow_mut().read(&mut result)?;
   |                        ^^^^
   |
   = note: the method `read` exists but the following trait bounds were not satisfied:
           `&mut &'s T : std::io::Read`
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following trait defines an item `read`, perhaps you need to implement it:
           candidate #1: `std::io::Read`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0599`.
error: Could not compile `hi`.

To learn more, run the command again with --verbose.

Other Options (not really future proof)
The other option, that I thought of would be to write a custom trait and implement it on each of the RefCell types manually (I didn’t check if the code below works)

trait RefCellTrait {
    fn borrow_mut(&self) -> RefMut<T>;
}
    
impl RefCellTrait for RefCell {}
impl RefCellTrait for PinCell {}
// ...

Or I could write an enum which contains all the types of RefCells

enum RefCellTypes<C> {
    RefCell(RefCell<C>),
    QCell(QCell<C>),
    // ...
}

impl<C> From<RefCell<C>> for RefCellTypes<C> {
    fn from(w: RefCell<C>) -> Self {
        RefCellTypes::RefCell(w)
    }
}
//...

The problem with these kinds of solutions is, that it’s very specific and new implementations can’t be used without adding them to my source code manually.

So before I continue experimenting with the BorrowMut trait I wanted to ask in the forum in the hope, that someone can help me out :hugs:

Hava a nice day

Lucas

This requires being able to describe “families of associated types”, which shall become easy to do with GATs. I don’t really know if it can be achieved without them, so I will ping the one who tries to have GAT-like traits without GATs: @KrishnaSannasi

2 Likes

Lol, thanks for the intro, ok

This does require some form of GATs, specifcally, this requires associated lifetimes.

First we will need to change RefCellTrait

trait RefCellTrait<'a> {
    type Target;
    type Pointer: 'a + DerefMut<Target = Self::Target>;
    
    fn borrow_mut(&'a self) -> Self::Pointer;
}

Then you can implement it like so

impl<'a, T: 'a> RefCellTrait<'a> for RefCell<T> {
    type Target = T;
    type Pointer = RefMut<'a, T>;
    
    fn borrow_mut(&'a self) -> Self::Pointer {
        RefCell::borrow_mut(self)
    }
}

you can implement your function like so

fn generic_function<'s, C, T>(value: &'s T) -> Result<Vec<u8>>
where C: Read + Seek,
      T: RefCellTrait<'s, Target = C> {
    let mut result = [0; 4];
    value.borrow_mut().read(&mut result)?;
    Ok(result.to_vec())
}

playground link

4 Likes

Wow, thanks the solution works :blush:

I read up a bit on how and why you use types in traits, but couldn’t find a way of eliding the lifetimes in a type.
The RefCell function is defined like this

fn try_borrow(&self) -> Result<Ref<'_, T>, BorrowError>;

but converting the trait like this

trait RefCellTrait {
    type Target;
    //type Pointer: 'a + DerefMut<Target = Self::Target>;
    
    fn borrow_mut(&self) -> RefMut<'_, Self::Target>;
}

does work, but only on a RefCell :roll_eyes: (because I can’t change the return type on the impl)
the following doesn’t compile, as there is a lifetime missing in the type

impl<T> RefCellTrait for RefCell<T> {
    type Target = T;
    type Pointer = RefMut<'_, Self::Target>;
    
    fn borrow_mut(&self) -> Self::Pointer {
         RefCell::borrow_mut(self)
    }
}

sorry for the weird mess with code examples I am on my phone and didn’t test for valid code.

You ned a lifetime parameter on the trait itself,

trait RefCellTrait<'a> {
    type Target;
    type Pointer: 'a + DerefMut<Target = Self::Target>;

    fn borrow_mut(&'a self) -> Self::Pointer;
}

impl<'a, T: 'a> RefCellTrait<'a> for RefCell<T> {
    type Target = T;
    type Pointer = RefMut<'a, Self::Target>;

    fn borrow_mut(&'a self) -> Self::Pointer {
        RefCell::borrow_mut(self)
    }
} 

This is because you need a way to name the lifetime of &self on the associated types, and the only way to do that right now is to put lifetimes on the trait.

Lifetime elision only works on function signatures, and no where else. This is because, even if the lifetimes are elided, it is easy to figure out the lifetime relationships between the inputs and outputs.