最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

rust - Best practice for self vs. static struct function to avoid duplicate borrow issue - Stack Overflow

programmeradmin3浏览0评论

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?

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?

Share Improve this question asked Feb 17 at 15:28 JohanJohan 3951 gold badge4 silver badges12 bronze badges 5
  • 1 I'd say making them not take self if not needed is quite idiomatic. Perhaps even make them free functions. – Chayim Friedman Commented Feb 17 at 15:33
  • 1 If you do need access to self, you can pass the fields as separate parameters. Alternatively, a trick you can use is to remove the entry from the map, then re-insert it. – Chayim Friedman Commented Feb 17 at 15:34
  • Method 2 is quite idiomatic. Note that you can use Self::helper2() instead of A::helper2(), which is significantly shorter if A has a long name and/or a bunch of generics. (Also, it makes it crystal-clear that you're invoking a non-self-taking method on the same type.) – user4815162342 Commented Feb 17 at 15:50
  • Thanks for the comments. I also found myself doing some refactoring to some less optimal code as ideally I wanted access to "self" in the helper2() to update some struct fields. Instead I have to propagate some values up to the calling func1() to be updated there instead. But I guess there are no ways around that. – Johan Commented Feb 17 at 16:31
  • @ChayimFriedman, This was actually my first solution but since it requires a O(lg(n)) insert operation (which is expensive as I'm chasing ns) not something I wanted permanently. – Johan Commented Feb 17 at 16:55
Add a comment  | 

1 Answer 1

Reset to default 0

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} }
发布评论

评论列表(0)

  1. 暂无评论