Cow 타입

우리는 어떤 값이 참조인가, 아니면 소유권을 가지고 있는가에 대해 코드상으로 알고 싶을 때가 있습니다.

std::borrow에 존재하는 ToOwned라는 트레잇이 존재합니다. ToOwned는 소유권이 있는 (owned) 타입으로 변환할 수 있는 트레잇입니다.

예를 들어, to_owned 함수를 사용하여, 참조 &str를 소유권이 있는 String으로 변환할 수 있습니다.

이를 이용해서 구현하면 좋을 듯한데, 이미 구현된 게 있으니: 바로 Cow (Copy On Write) 열거형입니다.

Copy On Write는 읽기만 필요한 경우, 굳이 대상을 다시 쓸 필요가 없으며, 수정이 있다면 그 대상을 새로 만드는 리소스 관리 기법입니다. (이 때문에 크기가 커질 수 있습니다.)

즉, Cow<T>는 읽기 전용입니다.

Cow<T> 열거형의 구현은 다음과 같습니다:

#![allow(unused)]
fn main() {
pub enum Cow<'a, B>
where
    B: 'a + ToOwned + ?Sized,
 {
    Borrowed(&'a B),
    Owned(<B as ToOwned>::Owned),
}
}

제네릭 B는 수명 'a, ToOwned?Sized로 바운드되어 있습니다.

B가 크기를 알 수 있는 타입인지 아닌지 모르니, ?Sized가 포함되었습니다.

예를 들어봅시다. Borrowed"Hello, World!", &'static str가 포함될 수 있습니다. 반면 StringOwned에 포함됩니다. 그 이유는, ToOwned 트레잇에 대해 &str은 다음과 같이 구현되어 있습니다:

#![allow(unused)]
fn main() {
impl ToOwned for str {
    type Owned = String;

    fn to_owned(&self) -> String {
        unsafe { String::from_utf8_unchecked(self.as_bytes().to_owned()) }
    }

    fn clone_into(&self, target: &mut String) {
        // ...
    }
}
}

연관 타입(associated type) OwnedString으로 명시되어 있습니다.

즉, StringB (&'static str)의 OwnedString이기 때문에, StringOwned에 포함됩니다.

use std::borrow::Cow;

fn foo(x: &str) -> Cow<'static, str> {
    if x == "foo" {
        Cow::Borrowed("bar")
    } else {
        Cow::Owned(x.to_string())
    }
}

fn main() {
    match foo("foo") {
        Cow::Borrowed(x /* &str */) => println!("Borrowed: {x}"),
        Cow::Owned(x /* String */) => println!("Owned: {x}"),
    }

    match foo("baz") {
        Cow::Borrowed(x /* &str */) => println!("Borrowed: {x}"),
        Cow::Owned(x /* String */) => println!("Owned: {x}"),
    }
}

이런 방법으로, 위에서 서술한 참조인가, 아니면 소유권을 가지고 있는 (owned) 값인가에 대해 알 수 있습니다.