다음과 같은 코드를 보자.
let some_1 = Some(1);
let some_1_ref = &some_1;
let some_2 = some_1_ref.map(|one| one + 1);
위 코드는 문제없이 컴파일이 된다. 당연한가? 만약 아무런 이상함도 못느꼈다면, 다음과 같은 코드를 보자.
let some_1 = Some("1".to_string());
let some_1_ref = &some_1;
let some_2 = some_1_ref.map(|one| "two".to_string());
이 코드는 컴파일되지 않는다.
error[E0507]: cannot move out of `*some_1_ref` which is behind a shared reference
--> src/first.rs:253:22
|
253 | let some_2 = some_1_ref.map(|one| "two".to_string());
| ^^^^^^^^^^^----------------------------
| | |
| | `*some_1_ref` moved due to this method call
| move occurs because `*some_1_ref` has type `Option<String>`, which does not implement the `Copy` trait
|
Option
의 map
은 ownership
을 갖는다. 따라서, some_1_ref
의 경우 shared reference이기 때문에 값을 move
할 수 없다.
그런데 왜 첫번째 예제는 됐을까? shared reference여도 값이 move
가 될 수 있는 경우가 있는데, 그건 바로 Copy
trait가 구현되었을 때다. 즉, 첫번째 에제는 Copy
trait을 구현하고 있다는 뜻이다.
Rust에서 Copy
trait을 구현하고 있는 타입들로만 이루어진 값의 경우, 자동으로 Copy
trait을 구현한다.
Option
, i32
는 Copy
trait을 구현하고 있기 때문에 첫번째 예제는 컴파일이 된 것이다. 그 반면에, String
은 Copy
trait를 구현하고 있지 않기 때문에 ownership
을 해결해야 한다.
그래서 보통 Option
과 map
을 사용할 때 as_ref()
메서드를 활용한다. 이는 &Option<T>
를 Option<&T>
로 변환해주는 유용한 메서드다.
let some_1 = Some("1".to_string());
let some_1_ref = &some_1;
let some_2 = some_1_ref.as_ref().map(|one| "two".to_string());
이제 컴파일이 될 것이다.
그렇다면 다음과 같은 코드는 어떨까?
// some_1이 &String을 갖게 해보자.
let one = "1".to_string();
let some_1 = Some(&one);
let some_1_ref = &some_1;
let some_2 = some_1_ref.map(|one| "two".to_string());
이번엔 또 컴파일이 된다.
왜냐하면, Rust에서 &T
타입은 모두 Copy
를 구현하기 때문이다. immutable reference이기 때문에 가능하다.
그렇다면 다음은 어떤가?
let mut one = "1".to_string();
// &mut 타입을 갖게 해보자
let some_1 = Some(&mut one);
let some_1_ref = &some_1;
let some_2 = some_1_ref.map(|one| "two".to_string());
이번엔 또 컴파일이 되지 않는다. &mut T
는 Copy
를 구현하지 않기 때문이다. mutable reference가 Copy
를 구현한다면 여러 곳에서 이 값을 수정함을 허용한다는 뜻인데, 애초에 Rust는 이를 허용하지 않는다.