파이썬에서


x = ["A", "B"]
x[0] = x[0] + x[1]


와 같은 코드를 러스트에서는 다음과 같이 쓴다.


fn main() {
let mut x = [String::from("A"), String::from("B")];
x[0] = x[0] + &x[1];
}


근데 숙련된 러스트 사용자라면 위 코드에서 에러가 나는 것을 안다. 왜냐하면 우변의 x[0] + &x[1] 에서 +연산자는 왼쪽 항(left hand side)인 x[0] 의 소유권을 consume하지만 배열은 partially move 될 수 없어 자신이 소유한 원소의 소유권을 넘길 수 없다. 그렇다면 다음처럼 AddAssign을 이용하면 어떨까?


fn main() {
let mut x = [String::from("A"), String::from("B")];
x[0] += &x[1];
}


이것 역시 에러가 난다. 보통 =인 대입은 move 연산으로 어떠한 함수도 호출하지 않지만 +=인 AddAssign은 함수이기 때문에 x[0] += &x[1];std::ops::AddAssign::add_assign(&mut x[0], &x[1]); 와 동일한 코드이다. 여기서 함수인자로 &mut x[0]&x[1]를 동시에 받아 x에 대한 mutable reference와 immutable reference를 같이 취하고 있어 borrow check에서 에러가 난다. 해결법은 무엇일까? 문제점의 시작은 x[0]x[1]가 다른 값을 가리킴에도 불구하고 두 값의 reference를 x로부터 빌렸다고 보기 때문에 그렇다. 변수 색인(index)의 경우 컴파일 시간에 둘이 같은지 다른지 확인할 수 없으므로 두 참조자는 x를 빌렸다고 보는 것이다. 이런 문제를 해결하는 방법은 slice를 split_at을 이용해서 나누는 것이다. split_at은 슬라이스를 2개의 슬라이스로 나누어준다.


fn main() {
let mut x = [String::from("A"), String::from("B")];
let (l, r) = x[..].split_at_mut(1);
l[0] += &r[0];
println!("{x:?}");
}


["AB", "B"]


원하는 결과가 잘 나온다. 다른 방법은 없을까? 간단한 방법이 있다


x[0] = format!("{}{}", x[0], x[1]);


x[0] = x[0].to_owned() + &x[1];


x[0] = std::mem::take(&mut x[0]) + &x[1];

// take는 내부적으로 <String as Default>::default()를 호출해 오버헤드가 있음.


위처럼 약간의 런타임 오버헤드를 감수하면 편리함이 온다. 앞으로 러스트를 하면서 이런 성능과 편리함의 유혹이 많을텐데 러스트에서는 일상이다. 둘 중 어느것을 취하든 나쁘다고 할 수 없다. 간단함과 성능을 모두 챙기고 싶다면 다음과 같이 사용하면 된다.


fn main() {
let mut x = [String::from("A"), String::from("B")];
// SAFETY : x[0] and x[1] are different values.
x[0] += unsafe { &*(&x[1] as *const String) };
println!("{x:?}");
}


fn main() {
let mut x = [String::from("A"), String::from("B")];
// SAFETY : x[0] and x[1] are different values. Transmute &str into the same type.
x[0] += unsafe { std::mem::transmute(x[1].as_str()) };
println!("{x:?}");
}


이 방법을 나는 참조자 세탁(reference laundering)이라고 부른다. 한 번만 포인터가 되었다가 reference로 돌아가면 borrow check를 피해갈 수 있다. 또는 std::mem::transmute::<&str, &str>() 로 참조자를 세탁해도 된다. 프로그래머가 안전하다고 확신이 들면 unsafe를 사용하며 주석으로 안전한 이유를 설명하자.


확인하기 문제: 1. 튜플이나 구조체의 경우에는 위 같은 문제가 발생하는가? 2. split_at은 런타임 오버헤드가 있는가?