Johan
Johan

Reputation: 397

Best practice for self vs. static struct function to avoid duplicate borrow issue

I have a problem I found a solution to but it is so ugly that there must be a better "idiomatic" way to solve it (I'm from a C++/Java background so I still fell the urge to make everything classes ...)

I have a method/functions which gets a problem with referencing mutable variables in the struct together with an mutable "self" reference.

The structure is something like this

pub struct A { map: BtreeMap<...>, }
impl A {
   fn helper1(&self, ...) { ... }
   fn helper2(&mut self, &mut map_entry ...) { 
      // Some work that adjusts struct entry "map_entry"
      self.helper1( &self, ...);
   }

   fn func1(&mut self, ...) {
      // Get some reference to a specific BTreeMap entry last_entry()
      self.helper2( self.map.get_last() );
      // Cleaning up etc.
    }

    fn func2(&mut self, ...) {
      // Get some other reference to a specific BTreeMap entry first_entry()
      self.helper2( self.map.get_first() );
      // Cleaning up etc.
    }

    fn func3(&mut self, ...) {
      self.helper2( ... some other calculations to determine map entry ...);
    }
}

Now, this will (of course) fail with a "Cannot borrow *self as mutable more than once ..." in func1,2,3() as I have both self and the variable from the struct (entry from the BTreeMap) as mutable. I fully (I think ...) understand the problem.

This reason for this error is me trying to break it down in multiple methods as a lot of the work can be factored out in the helper2() method which can be used from func1,2,3() and also wanting to keep it as a "class" method.

I can fix this in two ways

  1. duplicate code to avoid having to make calls to other methods and thereby avoiding the "self" borrow issue
  2. make all the helper methods into static functions to be called as A::helper2() and not having a "self" being borrowed more than one time in the func1,2,3() as they have no need to access any internal struct variables.

As I don't want to duplicate code I solved it with Method 2).

However, this seems quite "ugly" to me having to mix static functions with "class/object" functions.

Is it possible to give some generic advise (without having to go into more specific details in my case) about the idiomatic way to handle this more generic issue. It feels like this use case or situation wouldn't be that uncommon?

Upvotes: 0

Views: 58

Answers (1)

啊鹿Dizzyi
啊鹿Dizzyi

Reputation: 1025

Whenever I encounter this kind of problem, I knows I don't follow the rust way of thinking, I think the best option is to refactor your struct definition to separate the BTreeMap from the struct A to avoid self reference.

struct B {
    // ...
}
struct Map(BTreeMap<...>)

that way, inside B's function, you can &mut self all the way down, and take &mut Map, &mut Option<V>, &mut V, down each deeper call.

struct B {
    my_field: i32,
}
struct Map(BTreeMap<String, i32>); 
// that way you can have custom impl for this specific type of map
// avoid mix up with other string,i32 map

impl B {
    fn top(&mut self, map: &mut Map) {
        self.my_field += 1;
        self.mid(map.0.get_mut("foo"));
    }
    fn mid(&mut self, v: Option<&mut i32>) {
        self.my_field += 1;
        self.bot(v.unwrap());
    }
    fn bot(&mut self, v: &mut i32) {
        self.my_field += 1;
    }
}

if you really needed to store both the map and the data at the same struct just make a struct A {b:B,m:Map}.

If you cannot change the struct

here's an (crazy?) idea to refactor the snippet.

use impl Fn(&mut BTreeMap<K, V>) -> &mut V getter as arg for helper2.

that way you can get the reference to value within helper2, and don't have to worries about multiple borrow.

you can provide capture, fn or Self::fn as argument.

Example

use std::collections::BTreeMap;

fn get_bar(m: &mut BTreeMap<String, i32>) -> &mut i32 {
    m.get_mut("bar").unwrap()
}

#[derive(Debug)]
pub struct A {
    my_field: i32,
    map: BTreeMap<String, i32>,
}
impl A {
    fn helper1(&self) {
        println!("........helper 1");
    }
    fn helper2(&mut self, getter: impl Fn(&mut BTreeMap<String, i32>) -> &mut i32) {
        println!("....helper 2");

        let v = getter(&mut self.map);
        // self.map.get_mut("foo"); // <-- this will error, since multiple borrow

        // change the value
        *v += 1;

        let _ = v; // drop of value reference v
        self.map.get("foo"); // mut borrow possible again

        // mut reference of self
        self.my_field += 1;

        self.helper1()
    }

    // use capture
    fn fun1(&mut self) {
        println!("fun 1");
        self.helper2(|m| m.get_mut("foo").unwrap());
    }

    // use fn
    fn fun2(&mut self) {
        println!("fun 2");
        self.helper2(get_bar);
    }

    // use struct fn
    fn fun3(&mut self) {
        println!("fun 3");
        self.helper2(Self::get_baz);
    }
    fn get_baz(m: &mut BTreeMap<String, i32>) -> &mut i32 {
        m.get_mut("baz").unwrap()
    }
}

fn main() {
    let mut a: A = A {
        my_field: 0,
        map: BTreeMap::from_iter(
            [
                ("foo".to_string(), 69),
                ("bar".to_string(), 42),
                ("baz".to_string(), 123),
            ]
            .into_iter(),
        ),
    };

    println!("{:?}\n", a);
    a.fun1();
    println!("{:?}\n", a);
    a.fun2();
    println!("{:?}\n", a);
    a.fun3();
    println!("{:?}\n", a);
}
A { my_field: 0, map: {"bar": 42, "baz": 123, "foo": 69} }

fun 1
....helper 2
........helper 1
A { my_field: 1, map: {"bar": 42, "baz": 123, "foo": 70} }

fun 2
....helper 2
........helper 1
A { my_field: 2, map: {"bar": 43, "baz": 123, "foo": 70} }

fun 3
....helper 2
........helper 1
A { my_field: 3, map: {"bar": 43, "baz": 124, "foo": 70} }

Upvotes: 0

Related Questions