Is it a bad idea for a `Copy` type to have mutating methods?

I'm writing a Rust library that features a custom "set of enums" type (like a bitflags / enumflags2 / flagset set type, but with various customizations & optimizations specific to my use case). The type's fields are all Copy, so I could make the type itself Copy, but the type also has various mutating methods that take &mut self, and I worry that combining these two features might lead to misuse & accidental bugs by users.

This worry is almost entirely motivated by the recommendation against making iterators Copy. While that footgun is well-known enough to have a clippy lint, I can't find any pre-existing discussion about applying Copy to other mutable types.

I am aware that some of the abovementioned set-of-enum libraries use Copy types with mutating methods for their sets, but I can't tell how high that bridge they're jumping off of is, and thus I don't know whether to jump with them.

There is nothing wrong with mutating a Copy type. It happens all the time with structs that are Copy.

The restriction is that if you make something Copy then you can't put things in it that aren't Copy, such as a String. But if you have an object or container that can be Copy, there is no reason not to make it Copy, and there is no implication that it should not be mutated.


The confusion caused by making Iterators Copy is very specific to iterators or perhaps iterator-like things. See the next reply from @CAD97 for a better explanation.

I would say it's ill advised to have in-place mutating methods on Copy types if and only if it's unclear whether some methods are fn(self) -> Self (copy from the receiver) or fn(&mut self) -> &mut Self (mutate the receiver) shaped.

Iterators are specifically notable because whether some iterator method chain is effectively by-ref or by-move is type dependent (whether the used binding is impl Iterator or &mut impl Iterator). For concrete types this can't be the case because a specific method name only has one signature; it only causes this acute of a footgun when you have impl<T: Trait> Trait for &mut Trait.

The primitive numeric types (iN, uN, fN) have fn(&mut self) methods, e.g. add_assign (for +=). Various math libraries offer primarily by-value API, but also op_inplace variants when that can be beneficial to performance.

Implement Copy if the type is logically copyable. Although if you do have some in-place mutating methods, I'd probably recommend sticking #[must_use] on the by-copy methods so it's more difficult to call them while expecting them to be in-place without noticing.

7 Likes

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.