Что такое Cow (Clone on Write) в Rust и когда стоит его использовать?

В Rust Cow (сокращение от Clone on Write) — это умный указатель, позволяющий эффективно работать с данными, которые обычно не требуют клонирования, но при необходимости могут быть склонированы. Это гибкая структура, которая может прозрачно оборачивать как заимствованные (borrowed), так и владельческие (owned) данные, переключаясь между ними только при необходимости.

Тип Cow определён как:

enum Cow<'a, B: ?Sized + ToOwned> {
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}

Он доступен в стандартной библиотеке в модуле std::borrow.

Основная идея Cow

Когда у вас есть данные, которыми можно пользоваться по ссылке (например, &str, &[u8], &[T]), но в каких-то редких случаях может понадобиться их изменить, Cow позволяет:

  • по умолчанию работать с данными как с заимствованными (без аллокаций);

  • клонировать данные только в момент, когда они действительно модифицируются.

Таким образом, Cow помогает экономить память и ускорять выполнение в случаях, где копирование может быть дорогим и редко необходимым.

Как работает Cow

Рассмотрим простой пример:

use std::borrow::Cow;
fn modify(input: &str) -> Cow<str> {
if input.contains("!") {
let replaced = input.replace("!", ".");
Cow::Owned(replaced)
} else {
Cow::Borrowed(input)
}
}

В этом примере:

  • Если строка не содержит восклицательных знаков, Cow::Borrowed(input) возвращает ссылку — без копирования.

  • Если символ ! найден — создаётся новая строка, и возвращается Cow::Owned.

Снаружи Cow<str> можно использовать как обычную строку, например:

let result = modify("Hello, world!");
println!("{}", result); // если был '!', строка уже склонирована

Где применяется Cow

1. API, которые принимают как &T, так и T

Иногда вы хотите, чтобы функция принимала или ссылку, или владельца, в зависимости от ситуации:

fn process&lt;'a&gt;(input: Cow&lt;'a, str&gt;) {
println!("Processed: {}", input);
}

Теперь process(Cow::Borrowed("hello")) и process(Cow::Owned("hello".to_string())) оба допустимы.

2. Оптимизация чтения с условной мутацией

Например, если вы загружаете данные, обрабатываете и лишь изредка модифицируете — Cow помогает избежать копий:

fn sanitize&lt;'a&gt;(data: &'a str) -> Cow&lt;'a, str&gt; {
if data.trim() == data {
Cow::Borrowed(data)
} else {
Cow::Owned(data.trim().to_owned())
}
}

3. Хранение данных с экономией памяти

Если у вас есть структура, хранящая строки, и вы хотите избежать лишнего String, пока данные не модифицированы:

struct Person&lt;'a&gt; {
name: Cow&lt;'a, str&gt;,
}

Если имя не изменяется — вы храните &'a str. Если нужно обновить — Cow становится владельцем String.

Преимущества Cow

  • Гибкость: можно работать и со ссылками, и с владельческими данными.

  • Экономия ресурсов: копирование происходит только при необходимости.

  • Безопасность: остаётся в рамках системы владения и заимствования Rust.

  • Простота API: функция с Cow<'a, str> позволяет вызывать её как с &'static str, так и с String.

Когда стоит использовать Cow

Использование Cow оправдано в следующих случаях:

  1. Данные чаще читаются, чем модифицируются.

  2. Нужно избежать лишних аллокаций и копирования.

  3. Хотите создать API, совместимый и с &T, и с T без лишних аллок.

  4. В структуре данных нужно хранить заимствование, которое может стать владельческим.

Когда не стоит использовать Cow

  1. Если вы точно знаете, что всегда будете владеть данными — проще сразу использовать String, Vec<T> и т.п.

  2. Если данные почти всегда модифицируются — копия всё равно произойдёт, и Cow не даст выигрыша.

  3. Если важна производительность в критических местах — Cow добавляет небольшую обёртку (enum), и хотя она zero-cost, в hot path лучше использовать фиксированные типы.