Another possible option is to just not have the getter.
The main time getters are idiomatic in Rust is when you want to keep the field private to maintain an invariant or maximal flexibility, but need to allow other modules/crates to access the field. If this is true for you, getting rid of the getter probably isn't an option.
Even when you have a getter, it's more idiomatic to just access the field when possible, as that is more flexible with regards to the borrow checker. I thought perhaps there would be a lint for this, but I didn't find one.
For what it's worth, I tend to lean in this direction...
If the type might become non-Copy in the future, it could be prudent to omit the Copy implementation now, to avoid a breaking API change.
Start with enums non-Copy then add it if not having it becomes a burden or having it is obviously the correct choice.
A good example are state machine states (which I think is your example). Non-Copy has served me well. In the rare circumstances I've need to copy the value .clone() is not too much typing to be a burden and makes the intent crystal clear.
I'd say Copy is great. If you have a simple enum without data you should make it Copy, because it makes using it much easier, and possibly even a bit more efficient (since a copy of the value is smaller than a pointer).
The footgun here is more from having a in-place-modifying &mut self method on a Copy type. The mutate() could have been #[must_use] fn into_banana(self) -> Self.
Hmm, is there anything specifically because of the enum-ness that causes this? Wouldn't calling a &mut method on a non-enum (like a u32) have exactly the same behaviour?
I guess the Copy here is a red herring. Or at least it's surely not the only one responsible.
Sometimes, when you have a chain of errors leading to a bug, you can't clearly point to one of them and say "now that is the problem!".
I guess the nicest example of this phenomenon is shared mutability. Programmers have been arguing for decades whether it is sharing xor mutability that causes memory safety bugs:
"It's threads!" – shouted JavaScript and Python, and JS remained single-threaded, and Python introduced the GIL.
"It's mutability!" – screamed Haskell and Erlang, and they made (almost) everything immutable.
And then along came Rust, and said: "you are fools! You can have both sharing and mutability in the same language, as long as you isolate them from each other."
Similarly, I'd say a Copy enum is not a problem in itself. If it's a trivial enum, make it Copy. The root cause is calling a mutating method on a temporary, which is partially caused by the getter returning an owned value. (Returning owned values is not a problem in itself, either.)
If I write the equivalent code on the Playground, Clippy doesn't flag it, and I couldn't find a related lint. I'm not sure what the right solution here is, apart from simply not making a getter.
#[must_use] has a problem: “…wait, shouldn't I logically put this on almost every function? How much is too much?”. An analogous attribute for temporary-mutation would be the same way. The things which shouldn't get a warning are cases like std::cell::RefMut::deref_mut (used to mutate the underlying value that outlasts the RefMut) or std::process::Command::spawn (accepts &mut self but the builder is left semantically unchanged and may or may not be used); the majority of &mut self methods are not meaningful to use on a temporary.
Hm, considering those examples, that suggests a pattern: just warn on any call of a function with signature fn(&mut self, ...) -> () on a temporary. All of the examples above are cases where the function returns a non-() value. (It still might be a false positive on things which have a side effect outside of self, like a channel send() function does.)
Types whose values can be duplicated simply by copying bits.
I would interpret this to mean that if I copy a Copy type, the bits of the duplicate must remain the same at all times, so &mut self on a Copy type should be illegal.
I meant &mut that is not &mut self. But, yeah, the point is that there should be no difference, so that's not a useful distinction.
Another thought: if I'm obtaining &mut T, that would typically be so that I can change it and others see the change, right? Because if I don't want to change it, then &T is suffcient, and if I want to change it but no one will see the change, then I can just take ownership.
In that case, could we consider it wrong by the compiler to crate a temporary to enable a &mut T?
Well, &mut self can be implicit, that's probably why people recommend against it. Non-self &mut is explicit, so it's harder to get it wrong and confuse it with a copy.
I don't understand what you mean by this. Can you elaborate?
I read it is "there should be a warning for calling a &mut method on a temporary" (where a temporary is a value dropped at the end of the current expression, just to be explicit)
I think it would be a bit too noisy as a warning, but it might be a good default clippy. You generally don't want to mutate temporaries, most builders want to at least support moving ownership down the call chain so the final build call can take from the builder for example. Maybe nonstandard lock guards' methods would want this? (The standard ones can directly go to derefmut, which has clear semantics)
In any case, a Copy type very much seems like it has no business with mut methods, semantically. The exclusion implied by the mut has no actual effect and can be defeated by a simple wrapping {}
My point is exactly that it's not clearly/generally the case. Even implicit &mut self needs to be allowed if you want to implement something as trivial as AddAssign on primitive numeric types.
Incidentally, at $JOB, I wrote some generic signal processing code that needed to accumulate values of a generic type (for computing convolutions and related operations), and the best design for that was passing a &mut to the appropriate internal method of the filter data structure.