はじめに
入力をそのまま返すか、一部書き換えてから返すか、関数の中で判定する場面があります。素直に書くと「変更しない場合も無駄に String::clone() してしまう」、または「&str を返したいが、変更があった時は所有データを返したい」というジレンマに陥ります。
std::borrow::Cow<'_, T> (Clone-On-Write) はこのジレンマを解きます。借用 (Borrowed) と所有 (Owned) のどちらかを選んで返せる enum で、「必要になるまで allocate しない」が型で表現できます。
まず動くコード
「すでに全部大文字ならそのまま借用を返し、小文字を含む場合だけ大文字化した所有データを返す」関数。
use std::borrow::Cow; fn ensure_uppercase(input: &str) -> Cow<'_, str> { if input.chars().all(|c| !c.is_lowercase()) { Cow::Borrowed(input) // 変更不要 → 借用のまま返す } else { Cow::Owned(input.to_uppercase()) // 変更必要 → 所有データを作って返す } } fn main() { let a = ensure_uppercase("HELLO"); let b = ensure_uppercase("hello"); println!("a = {a}, borrowed? {}", matches!(a, Cow::Borrowed(_))); println!("b = {b}, borrowed? {}", matches!(b, Cow::Borrowed(_))); }
実行結果は次のようになります。
a = HELLO, borrowed? true b = HELLO, borrowed? false
a は元の "HELLO" をそのまま借りています (allocate なし)。b は "hello" を大文字化した新しい String を持っています (allocate あり)。同じ "HELLO" という文字列でも、片方は借用、片方は所有。Cow がこの違いを型ひとつで表現できるのがポイントです。
Cow の中身
Cow の定義はこうです (簡略化)。
pub enum Cow<'a, B: ?Sized + ToOwned + 'a> { Borrowed(&'a B), Owned(<B as ToOwned>::Owned), }
B は借用形態 (str, [T], ...) 、<B as ToOwned>::Owned がそれを所有する型 (String, Vec<T>) です。Cow<'a, str> なら Borrowed(&'a str) または Owned(String)。
利用者は Cow を Deref 経由で透過的に扱えます。&str が必要なメソッドはそのまま呼べます。
let s = ensure_uppercase("Hello"); println!("len = {}", s.len()); // どちらの variant でも動く println!("upper = {}", s.to_uppercase()); // どちらでも動く
「allocate されているかどうか」を呼び出し側が気にする必要はありません。
to_mut で「必要になったら所有に切り替える」
Cow::to_mut(&mut self) を呼ぶと、Borrowed 状態だった場合だけ所有データに切り替えて、&mut Owned を返します。
use std::borrow::Cow; fn append_exclamation(input: &str) -> Cow<'_, str> { if input.ends_with('!') { Cow::Borrowed(input) } else { let mut owned = Cow::Owned(input.to_owned()); owned.to_mut().push('!'); owned } } fn main() { println!("{}", append_exclamation("hi")); // hi! println!("{}", append_exclamation("yo!")); // yo! }
これは「すでに所有しているなら borrow から脱出して直接書き換える」のではなく、「Borrowed の場合だけ所有版へ変換し書き換え可能にする」という挙動です。
関数の戻り値型として使う
Cow の真価は 戻り値型 で出ます。「入力をそのまま返したい場合もあれば、変更を加えた所有版を返したい場合もある」関数。
use std::borrow::Cow; fn resolve_greeting<'a>(default: &'a str, override_with: Option<&str>) -> Cow<'a, str> { override_with.map_or(Cow::Borrowed(default), |s| Cow::Owned(s.to_owned())) } fn main() { let g1 = resolve_greeting("hi", None); let g2 = resolve_greeting("hi", Some("yo")); println!("g1 = {g1}, borrowed? {}", matches!(g1, Cow::Borrowed(_))); println!("g2 = {g2}, borrowed? {}", matches!(g2, Cow::Borrowed(_))); }
設定に上書きが指定されていなければ default をそのまま借用、指定されていれば所有データを作る。「上書きされなかった99%のケースで allocate が起きない」 設計が可能になります。
いつ Cow を使うか
入力データの大半をそのまま使いたいが、まれに変更が必要 な場面で効きます。具体例を挙げます。
- HTML エスケープ: 危険文字を含まなければ借用のまま、含めばエスケープ後の所有データを返す
- パスの正規化: すでに正規化されているならそのまま、必要なら正規化後を返す
- 設定の上書き: 上書きされていなければ default を借用、上書きされたら所有
- テンプレート展開: プレースホルダがなければ借用、あれば展開後の所有データを返す
逆に「常に変更する」ような関数では、最初から String を返すほうが素直です。Cow は 「変更しないことが多数派」 な場面で意味があります。
Cow<'_, [T]> も同じ仕組み
Cow は文字列以外でも使えます。Cow<'_, [T]> で「Vec の借用 / 所有」を切り替えられます。
use std::borrow::Cow; fn ensure_sorted(input: &[i32]) -> Cow<'_, [i32]> { if input.windows(2).all(|w| w[0] <= w[1]) { Cow::Borrowed(input) } else { let mut owned = input.to_owned(); owned.sort_unstable(); Cow::Owned(owned) } } fn main() { let a = ensure_sorted(&[1, 2, 3]); let b = ensure_sorted(&[3, 1, 2]); println!("{a:?}, borrowed? {}", matches!(a, Cow::Borrowed(_))); println!("{b:?}, borrowed? {}", matches!(b, Cow::Borrowed(_))); }
すでに昇順なら借用のまま、そうでなければソートして所有版を返す。
まとめ
Cow<'_, T>はBorrowed(&T)とOwned(<T as ToOwned>::Owned)の enum- 「変更が必要なときだけ allocate する」を型で表現できる
- 戻り値型として使うと「変更しないことが多数派」な関数で allocate を回避できる
to_mut()で必要になったタイミングで所有版に切り替えられる&str/&[T]どちらでも同じ仕組み
Cow を使うべきか迷ったら、 「この関数の戻り値、入力をそのまま返したいケースが半分以上あるだろうか」を考えてみてください。Yes なら Cow の出番です。
関連
std::borrow::Cow: https://doc.rust-lang.org/std/borrow/enum.Cow.htmlstd::borrow::ToOwned: https://doc.rust-lang.org/std/borrow/trait.ToOwned.html