[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"
$ 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?
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.
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.
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.
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.
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.
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.