Regarding binary size… there’s probably a good chance that identical code between Foo<&Bar>
and Foo<&mut Bar>
might get deduplicated anyways, if you take an approach such as e.g. the impl<B> Foo<B> where B: Deref<Target = Bar>
one. Though I might be expecting too much from the optimizer here, I’m not entirely certain; one would have to inspect the assembly 
Another approach would be to try implementing the Foo<&mut Bar>
methods in terms of the Foo<&Bar>
ones using some kind of shim. E.g. a function like
impl Foo<&mut Bar> {
fn reborrow_immutable(&self) -> Foo<&Bar> {
Foo {
index: self.index,
buf: &*self.buf,
}
}
}
would allow a &self
method to be implemented like
impl Foo<&Bar> {
fn foo(&self) { …long code… }
}
impl Foo<&mut Bar> {
fn foo(&self) {
self.reborrow_immutable().foo()
}
}
so that if you have - say - a macro generate code like this, it’s an even better chance to avoid code bloat.
Of course, for &mut self
methods, e.g. mutating the indices, this doesn’t quite work… you could perhaps have something with a callback 
impl Foo<&mut Bar> {
fn with_reborrow_immutable<R>(&mut self, f: impl FnOnce(&mut Foo<&Bar>) -> R) -> R {
let mut foo = Foo {
index: self.index,
buf: &*self.buf,
};
let r = f(&mut foo);
self.index = foo.index;
r
}
}
impl Foo<&Bar> {
fn foo(&mut self) { …long code… }
}
impl Foo<&mut Bar> {
fn foo(&mut self) {
self.with_reborrow_immutable(|this| this.foo())
}
}
though this does involve some additional code to write back the modifications to the index… maybe even better could be an approach that does de-duplicate using a helper function. E.g. your methods could be written like this
impl<B> Foo<B> where B: Deref<Target = Bar> {
fn foo(&mut self, s: String) -> Result<(), ()> {
fn foo(index: &mut (usize, usize), buf: &Bar, s: String) -> Result<(), ()> {
// …long code here…
Ok(())
}
foo(&mut self.index, &*self.buf, s)
}
}
with the effect that the inner foo
isn’t monomorphized for different B
to begin with. (This style of code could be macro-generated, too.)