Compilation: partially moved due to this method call

Hi,

This is my MWE:

Cargo.toml:

[package]
name = "spin"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
spinners = "2.0.0"

src/main.rs:

use spinners::{Spinner, Spinners};

struct GlobalState {
    pub spinner: Spinner,
    pub count: usize,
}

impl GlobalState {
    fn doit(&mut self) {
        //self.spinner.message("Boo".to_string());
        self.count = 1;
    }
    fn print_count(&self) {
        println!("count: {}", self.count);
    }
}

fn main() {
    let mut g = GlobalState {
        spinner: Spinner::new(&Spinners::Moon, "".into()),
        count: 0,
    };
    g.doit();
    g.spinner.stop();
    g.print_count();
}

Errors:

$ cargo build   
   Compiling spin v0.1.0 (/home/alexey/spaces/rust/spin)
error[E0382]: borrow of partially moved value: `g`
  --> src/main.rs:25:5
   |
24 |     g.spinner.stop();
   |               ------ `g.spinner` partially moved due to this method call
25 |     g.print_count();
   |     ^^^^^^^^^^^^^^^ value borrowed here after partial move
   |
note: this function takes ownership of the receiver `self`, which moves `g.spinner`
  --> /home/alexey/.cargo/registry/src/github.com-1ecc6299db9ec823/spinners-2.0.0/src/lib.rs:40:17
   |
40 |     pub fn stop(self) {
   |                 ^^^^
   = note: partial move occurs because `g.spinner` has type `Spinner`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0382`.
error: could not compile `spin` due to previous error

Hopefully, my intent is clear: I just want to have my ad hoc global state, change it if necessary, and call its methods. The trouble with methods won't go away, no matter what I do. What does the compiler want of me?

The compiler error message shows that the stop method call takes ownership of it's argument. That means you cannot use g any more after calling stop, as you have moved spinner out of g, and not replaced it with anything. You have effectively (partially) destroyed your global state, so it can no longer be used.

The compiler doesn't allow objects that have been destroyed, or partially destroyed, to be used.

Do you mean that I want something thoroughly abominable and have to drop the idea altogether? Or is there a way to modify the approach so me and the compiler become happy?

Well, you could do various things. You could remove g.spinner from g and replace it with a new one before calling stop.

That crate seems a bit strange to me, is it your own crate? I haven't quite figured out what it is meant to be doing yet.

Do you mean this? No, this is not mine. It makes console spinners, rather pretty. It works.

Oh, I just noticed it depends on another crate called spinner rather than spinners.

One thing you could do is move the call

g.print_count();

up before the line

g.spinner.stop();

It all depends what you want to do overall.

I want to use GlobalState methods indiscriminately. After the g.spinner.stop();, too. This is merely a minimal working example. I have the problem in real life.

Well one way would be something like this:

let new = Spinner::new(&Spinners::Moon, "".into());
let old = std::mem::replace( &mut g.spinner, new );
old.stop();

But it's hard to give a solution without more information. Another way would be to use an option:

pub spinner: Option<Spinner>,

Then you can replace it with None when you don't want it any more...

1 Like

But it's hard to give a solution without more information.

This is all the info I can give. I want to call g.anymember() anywhere inside main(), regardless of the g.spinner calls.

The replacement solution looks way too ugly :smile_cat:

Do you understand the problem? It involves the idea of values being moved, when you move a value out of a struct, the struct can no longer be used, unless you replace what was moved out with something. The reason is the struct now contains an undefined value.

Reading the documentation here may help:

It sounds like you need to decide whether the spinner needs to start again. If you want to keep the global state and have the spinner sometimes spinning and sometimes not, then go with the Option<Spinner>, since your current design makes that functionality impossible. I presume you don't want the spinner running always, or you wouldn't be calling stop, which means that you can't have a Spinner in your global state, since as long as a Spinner exists, it will be spinning.

1 Like

Spinner is spent after stop(). I don't need it anymore. I object against its afterlife.

That's precisely the issue, your code insists on keeping a non existent Spinner. No ghosts allowed in data structures that aren't also dead.

Edit: Having taken a glance at spinners and spinner (upon which spinners is based), I'd recommend avoiding them. They've both got design issues related to drop, and spinner seems to be actually dead. So you've got multiple afterlife issues going on.

geebee22 mentioned Option<Spinner>.

Did he mean something like this:

struct GlobalState {
    pub spinner: Option<Spinner>,
    pub count: usize,
}

I never managed beyond this:

fn main() {
    let mut g = GlobalState {
        spinner: Some(Spinner::new(&Spinners::Moon, "".into())),
        count: 0,
    };
    g.doit();
    g.stop_spinner();
    g.print_count();
}

I am not really sure of anything...

UPD:

The same error:

...
    fn stop_spinner(&self) {
        match &self.spinner {
            Some(spinner) => spinner.stop(),
            _ => (),
        }
    }
error[E0507]: cannot move out of `*spinner` which is behind a shared reference
  --> src/main.rs:21:30
   |
21 |             Some(spinner) => spinner.stop(),
   |                              ^^^^^^^^^^^^^^ move occurs because `*spinner` has type `Spinner`, which does not implement the `Copy` trait

Does it take a touch more?

No "cleanup" is happening inside stop_spinner(), obviously.

You'd still need to use replace, but you'd replace the spinner with None. The replace is needed due to the poor design of spinners.

I've been trying just this, but can't advance beyond errors :slight_smile: . Please, make a hint.

let old = std::mem::replace( &mut g.spinner, None);
if let Some (spinner) = old { spinner.stop(); }

Edit or more compactly perhaps

if let Some (spinner) = std::mem::replace(&mut self.spinner, None); { 
  spinner.stop();
}

Here is a complete example using Option<Spinner> (writing a little stub so the code can be tested without the spinners library):

// stands in for the library
struct Spinner;
impl Spinner {
    fn new() -> Self { Spinner }
    fn message(&self) {}
    fn stop(self) {}
}

struct GlobalState {
    pub spinner: Option<Spinner>,
    pub count: usize,
}

impl GlobalState {
    fn doit(&mut self) {
        if let Some(spinner) = &self.spinner {
            spinner.message();
        }
        self.count = 1;
    }
    fn print_count(&self) {}
}

fn main() {
    let mut g = GlobalState {
        spinner: Some(Spinner::new()),
        count: 0,
    };
    g.doit();
    if let Some(spinner) = g.spinner.take() {
        spinner.stop();
    }
    g.print_count();
}

Option::take() is equivalent to the use of std::mem::replace discussed above but more convenient when you have an Option in particular.

Note that each place self.spinner is used you have to decide what you want to do if the Spinner is gone before you intended it to be — in this example, they will just do nothing. You could also choose to panic by using .unwrap() instead of if let.

1 Like

geebee22, droundy, kpreid, thank you all!

Even this will do:

    fn doit(&mut self) {
        self.spinner.as_ref().unwrap().message("Boo".to_string());
        self.count = 1;
    }

It's just panicking on a dead and buried spinner.

The same with a good message:

    fn doit(&mut self) {
        match &self.spinner {
            Some(spinner) => spinner.message("Boo".to_string()),
            _ => panic!("GlobalState::spinner is already dead."),
        };
        self.count = 1;
    }