Generalising over shared and mutable references


#1

I have to following piece of code:

fn foo<F>(data: &mut i32, f: F)
where
    F: FnOnce(&mut i32),
{
    f(data);
    bar(data);
}

fn bar(_: &i32) {}

fn main() {
    let mut a = 32;
    foo(&mut a, |_| ())
}

Since bar only needs a &i32 and F might not need a mutable reference, I’m trying to allow foo to take both shared and mutable references. I tried the following change:

fn foo<B, F>(data: B, f: F)
where
    B: Borrow<i32>,
    F: FnOnce(B),
{
    f(data);
    bar(data.borrow());
}

Since B is no longer a reference, the compiler doesn’t reborrow after calling f and I get a use-after-move error. (Playground link)

Is there a reasonable way to make this work?


#2

Abstracting over shared vs mutable references is, generally speaking, not going to work out. You can abstract over owned vs borrowed, but not shared vs unique. This is why you see a ton of foo() and foo_mut() patterns in APIs.

In your attempt, F wouldn’t be able to get a mutable borrow anyway - you’d need BorrowMut instead.

It’s a bit hard to suggest anything concrete without more details on your usecase. Is it literally something like your example or is there a more involved situation? You can consider using a macro instead: example


#3

There are no further details (other than additional parameters and more sophisticated work done by those functions). I guess I’ll stick with passing a mutable reference, at least until the it becomes unwieldy to provide one. Thanks!


#4

I guess one other thing you can do is change F to be FnOnce(B) -> B and then:

let data = f(data);
bar(data.borrow());

As mentioned, f won’t be able to get a mutable reference.