Adapt type inferrence to facilitate method chaining through BorrowMut<T> (and Borrow<T>)

Fairly generic method chaining is possible through BorrowMut, but the construction is not as ergonomic as it could be, because type inference forces us to be extra explicit:

use std::borrow::BorrowMut;

#[derive(Debug)]
struct Foo {
    bar: usize
}

impl Foo {
    fn new(bar: usize) -> Self {
        Self { bar }
    }
}

// We can implement relatively nice method chaining through
// implementing functions in a helper trait
trait FooImpl: BorrowMut<Foo> + Sized {
    fn set_bar(mut self, bar: usize) -> Self {
        self.borrow_mut().bar = bar;
        self 
    }
}

impl<T: BorrowMut<Foo> + Sized> FooImpl for T {}

fn main() {
    let mut x = Foo::new(0).set_bar(1);
    // Unfortunately, using the helper trait is not quite straightforward:
    // x.set_bar(2); // error[E0382]: borrow of moved value: `x`
    // I guess the rust compiler infers the type of self to be `Foo` instead of `&mut Foo`,
    // so we have to make it explicit
    (&mut x).set_bar(2);

    // Though even with this alteration, there are still obstackles, e.g. using this directly with Box does not
    // work because &mut Box<T> does not implement BorrowMut<T>
    let mut y = Box::new(Foo::new(0).set_bar(1));
    // y.set_bar(2); // error[E0382]: borrow of moved value: `y`
    // (&mut y).set_bar(2); // error[E0507]: cannot move out of a mutable reference
    y.as_mut().set_bar(2);

    println!("x={x:?} y={y:?}");
}

// If the rust compiler supported BorrowMut as an allowed type for self, we could make the entire
// construction even more ergonomic:
// impl Foo {
//     fn set_bar<T: BorrowMut<Self>>(mut self: T, bar: usize) -> T { // error[E0307]: invalid `self` parameter type: `T`
//         self.borrow_mut().bar = bar;
//         self 
//     }
// }
1 Like

You are over-engineering the API. There is value in keeping it simple, and only using generics when required. The following doesn't support chaining, but it is dead-simple, easy to understand, and works with owned and borrowed values (including &mut Box<Foo>) thanks to Deref coercion:

impl Foo {
    fn set_bar(&mut self, bar: usize) {
        self.bar = bar;
    }
}

The Deref coercion rules cannot be changed, as that would be a breaking change.

BorrowMut is not a type though, it is a trait.

It would be great to have some solution for fluid/builder interfaces, without having to choose move vs borrow for the entire interface.

The BorrowMut trick already works for standalone functions, but these don't have the desired usability.

Having arbitrary generic self type is another level from: