How to chain from struct itself

Hello,

I'm exploring mutable limitation of rust constraints. I understand chain method by returning the modified object, like this:

use std::borrow::BorrowMut;

struct MyStruct {
    pub counter: i32,
}

fn modify (mut my_struct: MyStruct) -> MyStruct {
    my_struct.counter += 1;
    my_struct
}

fn main() {
    let mut my_struct = MyStruct {counter: 0};
    my_struct = modify(my_struct);
    my_struct = modify(my_struct);
}

But how to deal with it when call of external function like modify is called from MyStruct ? Example:

use std::borrow::BorrowMut;

struct MyStruct {
    pub counter: i32,
}

fn modify (mut my_struct: MyStruct) -> MyStruct {
    my_struct.counter += 1;
    my_struct
}

impl MyStruct {
    fn work(&mut self) {
        self = modify(self);
        self = modify(self);
    }
}

fn main() {
    let mut my_struct = MyStruct {counter: 0};
    my_struct.work();
}
error[E0308]: mismatched types
  --> src/main.rs:14:23
   |
14 |         self = modify(self);
   |                       ^^^^ expected struct `MyStruct`, found `&mut MyStruct`

Thanks by advance !

Well, first of all,

fn work(&mut self) { ... }

is an abbreviation for

fn work(self: &mut Self) { ... }

which in turn stands for

fn work(self: &mut MyStruct) { ... }

in the context of an impl MyStruct.

So self is a mutable reference to MyStruct, i.e. a &mut MyStruct.
This means, that in order to pass a MyStruct and not a &mut MyStruct to the modify function, since modify expects a MyStruct, you cannot pass self instead, but e.g. need to dereference the reference self. Something like

*self = modify(*self);

This doesn’t quite compile yet, though.

The problem now is that you cannot easily take the value out of a &mut MyStruct reference. At least for a type that isn’t Copy the Copy trait allows you to, among other things, do something like for example dereferencing a &mut i32 by copying the number behind the reference.

In order to do *self = modify(*self), using Rust’s move semantics, it would need to be possible to:

  1. take/move the value out of the self: &mut MyStruct reference
  2. then call modify
  3. finally, move the result back into the place that self points to

however, if modify were to exit with a panic, then work would leave the reference self pointing to no valid value, which doesn’t work.

The usual workaround for something like this is to use mem::replace or something similar, and put in an intermediate dummy value. E.g.

fn work(&mut self) {
    let this = std::mem::replace(self, MyStruct { counter: 0 });
    let this = modify(this);
    *self = modify(this);
}

Another alternative is to copy the entire struct, e.g. like this

fn work(&mut self) {
    let new_self = modify(MyStruct {
        counter: self.counter,
    });
    *self = modify(new_self);
}

or by adding the Clone and/or Copy trait:

#[derive(Clone)]
struct MyStruct {
    pub counter: i32,
}

// ...
fn work(&mut self) {
    let new_self = modify(self.clone());
    *self = modify(new_self);
}

or

#[derive(Clone, Copy)]
struct MyStruct {
    pub counter: i32,
}

// ...
fn work(&mut self) {
    *self = modify(*self);
    *self = modify(*self);
}

The arguably more sane thing to do here though is to change modify into taking &mut MyStruct itself, too, i.e.


struct MyStruct {
    pub counter: i32,
}

fn modify (my_struct: &mut MyStruct) {
    my_struct.counter += 1;
}

impl MyStruct {
    fn work(&mut self) {
        modify(self);
        modify(self);
    }
}

fn main() {
    let mut my_struct = MyStruct {counter: 0};
    my_struct.work();
}
4 Likes

Here's an article by Niko on this topic.

Thanks for your time. I wil study all of that !

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.