개요
우리는 어떤 값이 참조인가, 아니면 소유권을 가지고 있는가에 대해 코드상으로 알고 싶을 때가 있습니다.
std::borrow
에 존재하는 ToOwned
라는 트레잇이 존재합니다.
ToOwned
는 소유권이 있는 (owned
) 타입으로 변환할 수 있는 트레잇입니다.
예를 들어, to_owned
함수를 사용하여, 참조 &str
를 소유권이 있는 String
으로 변환할 수 있습니다.
이를 이용해서 구현하면 좋을 듯한데, 이미 구현된 게 있으니: 바로 Cow (Copy On Write) 열거형입니다.
Copy On Write
는 읽기만 필요한 경우, 굳이 대상을 다시 쓸 필요가 없으며, 수정이 있다면 그 대상을 새로 만드는 리소스 관리 기법입니다. (이 때문에 크기가 커질 수 있습니다.)즉,
Cow<T>
는 읽기 전용입니다.
Cow<T>
열거형의 구현은 다음과 같습니다:
1
2
3
4
5
6
7
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
가 포함될 수 있습니다.
반면 String
은 Owned
에 포함됩니다. 그 이유는, ToOwned
트레잇에 대해 &str
은 다음과 같이 구현되어 있습니다:
1
2
3
4
5
6
7
8
9
10
11
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
) Owned
가 String
으로 명시되어 있습니다.
즉, String
은 B
(&'static str
)의 Owned
가 String
이기 때문에, String
은 Owned
에 포함됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
) 값인가에 대해 알 수 있습니다.