Cut Down On Clones With Cows

Ryan James Spencer

At the start of a program, it is straightforward to clone data all over the place to get things working, and, soon enough, the program is overrun by them. Switching away from clones can be hard because it requires fighting with the borrow checker, and alternative solutions aren't quite right for the job. How do you cut down allocations from cloning as if you were borrowing without winding up in borrow hell? Consider using a Cow.

Cow stands for Clone on Write and they are underrated for what they can do in this regard. On their own cows are usually larger than their owned or borrowed variants, but cutting down careless memory allocations may help improve performance.

Use a Cow when there is data allocated outside of a function or block, and there is some runtime logic that determines whether a write takes place. Cows are a useful mechanism for transferring ownership lazily by cloning data once and only once.

Cows are like hybrids such that at runtime, they might be borrowing data that's already been around, or they may be handing out borrows to an owned piece of data that they own.

Consider a function that replaces values in a string that we already have allocated outside of the function. Replacing characters might mean changing the size of the string or it could be a case of soft failure where we replace invalid characters with the unicode replacement character U+FFFD, as is the case for String::from_utf8_lossy. We should strive to recycle what values are already hanging around if we can help it. We can recycle in other ways, such as taking a reference to a default rather than assuming it must always be allocated on the fly, or having a collection lazily clone and take ownership of values as it needs to rather than cloning the collection from the start. With a bit of imagination, there are several places that Cows can be used to improve performance and cut down on clones.