[Rust] 러스트노미콘: Borrow 분할 (번역)
https://doc.rust-lang.org/nomicon/borrow-splitting.html
가변 참조들의 상호 배제 속성은 복합적인 구조체를 작업할때 한계가 있을 수 있습니다.
borrow 체커는 몇가지 기본적인 사항은 이해를 하고 있어요. 그래서 간단한건 꽤 쉽게 넘어갈 겁니다.
요즘의 borrow 체커는 구조체의 분리된 필드를 동시에 빌릴 수 있다는 것을 잘 알고 있습니다.
그래서 오늘날에는 이게 동작합니다.
**struct Foo { **
** a: i32, **
** b: i32, **
** c: i32, **
} ㅤ
**let mut x = Foo {a: 0, b: 0, c: 0}; **
**let a = &mut x.a; **
**let b = &mut x.b; **
**let c = &x.c; **
***b += 1; **
**let c2 = &x.c; **
***a += 10; **
**println!("{} {} {} {}", a, b, c, c2); **
하지만 배열이나 슬라이스에 대해서는 잘 동작하지 않을 거에요.
**let mut x = [1, 2, 3]; **
**let a = &mut x[0]; **
**let b = &mut x[1]; **
**println!("{} {}", a, b); **
**error[E0499]: cannot borrow x[..] as mutable more than once at a time --> src/lib.rs:4:18 | 3 | let a = &mut x[0]; | ---- first mutable borrow occurs here 4 | let b = &mut x[1]; | ^^^^ second mutable borrow occurs here 5 | println!("{} {}", a, b); 6 | } | - first borrow ends here error: aborting due to previous error **
borrow 체커가 이런 간단한 경우를 이해하는건 그럴듯하죠. 그런데 트리 같은 일반적인 컨테이너 타입들을 보면... 좀 절망적입니다. 불연속적이고, 컴파일타임에 확인하기가 어렵죠.
이런 경우 borrow 체커에게 우리가 하는게 괜찮다는 것을 가르쳐주려면, unsafe 코드로 내려갈 필요가 있습니디.
예를 들어 가변 슬라이스는 슬라이스를 전달받고(consume) 두 개의 가변 슬라이스를 반환하는 split_at_mut 함수를 제공합니다.
하나는 인덱스 왼쪽의 모든 항목에 대한 것이고, 다른 하나는 오른쪽의 모든 항목에 대한 것입니다.
직관적으로 우리는 슬라이스가 겹치지 않기 때문에 안전하다는 것을 알고 있죠.
하지만 구현은 일부의 unsafe함을 요구합니다.
**pub fn split_at_mut(&mut self, mid: usize) -> (&mut [T], &mut [T]) { **
** let len = self.len(); **
** let ptr = self.as_mut_ptr(); **
** unsafe { **
** assert!(mid <= len); **
** (from_raw_parts_mut(ptr, mid), [from_raw_parts_mut(ptr.add](http://from_raw_parts_mut(ptr.add)(mid), len - mid)) **
** } **
**} **
이건 실제로 약간 미묘합니다.
두 개의 &mut을 동일한 값으로 만드는 것을 피하기 위해 생포인터로 새 슬라이스를 명시적으로 생성합니다.
그런데 더 미묘한 것은 가변 참조를 생성하는 반복자가 작동하는 방식입니다.
반복자 trait은 다음과 같이 정의됩니다.
**trait Iterator { **
** type Item; **
** fn next(&mut self) -> OptionSelf::Item; **
**} **
주어진 정의대로라면, Self::Item은 self와의 연결이 없죠.
즉, 연속으로 여러 번 호출해 모든 결과를 동시에 보관할 수 있다는 겁니다.
이는 명확히 이러한 의미를 갖는 값별 반복자에 대해 완전히 괜찮습니다.
공유된 참조의 경우에도 실제로 괜찮습니다.
동일한 항목에 대해서 임의의 많은 참조를 허용하기 때문입니다. 이때 반복자는 공유되는 항목과 별도의 객체여야 해요.
그런데 가변 참조는 이걸 엉망으로 만듭니다. 언뜻보기에는 동일한 객체에 대한 여러 가변 참조를 생성하기 때문에 이 API와 완전히 호환되지 않는 것처럼 보일 수 있습니다!
하지만 반복자가 원샷(one-shot) 객체이기 때문에 실제로 작동합니다.
IterMut이 산출하는 모든 것은 최대 한 번만 산출되므로, 실제로 동일한 데이터에 대해 여러 개의 가변 참조를 산출하지는 않습니다.
놀랍게도 가변 반복자는 여러 타입에 대해 unsafe 코드를 구현할 필요가 없습니다!
예를 하나 들어볼까요?
여기에 단일 연결리스트가 있습니다.
**type Link
**struct Node
** elem: T, **
** next: Link
**} **
**pub struct LinkedList
** head: Link
**} **
**pub struct IterMut<'a, T: 'a>(Option<&'a mut Node
**impl
** fn iter_mut(&mut self) -> IterMut
** [IterMut(self.head.as_mut().map](http://IterMut(self.head.as_mut().map)(|node| &mut **node)) **
** } **
**} **
**impl<'a, T> Iterator for IterMut<'a, T> { **
** type Item = &'a mut T; **
** fn next(&mut self) -> OptionSelf::Item { **
** self.0.take().map(|node| { self.0 = node.next.as_mut().map(|node| &mut **node); &mut node.elem }) **
** } **
**} **
이제 이건 가변 슬라이스입니다.
**use std::mem; **
**pub struct IterMut<'a, T: 'a>(&'a mut[T]); **
**impl<'a, T> Iterator for IterMut<'a, T> { **
** type Item = &'a mut T; **
** fn next(&mut self) -> OptionSelf::Item { **
** let slice = mem::replace(&mut self.0, &mut []); **
** if slice.is_empty() { **
** return None; **
** } **
** let (l, r) = slice.split_at_mut(1); **
** self.0 = r; l.get_mut(0) **
** } **
**} **
**impl<'a, T> DoubleEndedIterator for IterMut<'a, T> { **
** fn next_back(&mut self) -> OptionSelf::Item { **
** let slice = mem::replace(&mut self.0, &mut []); **
** if slice.is_empty() { **
** return None; **
** } **
** let new_len = slice.len() - 1; **
** let (l, r) = slice.split_at_mut(new_len); **
** self.0 = l; r.get_mut(0) **
** } **
**} **
이건 이진트리고요.
**use std::collections::VecDeque; **
**type Link
**struct Node
** elem: T, **
** left: Link
** right: Link
**} **
**pub struct Tree
** root: Link
**} **
**struct NodeIterMut<'a, T: 'a> { **
** elem: Option<&'a mut T>, **
** left: Option<&'a mut Node
** right: Option<&'a mut Node
**} **
**enum State<'a, T: 'a> { **
** Elem(&'a mut T), **
** Node(&'a mut Node
**} **
**pub struct IterMut<'a, T: 'a>(VecDeque<NodeIterMut<'a, T>>); **
**impl
** pub fn iter_mut(&mut self) -> IterMut
** let mut deque = VecDeque::new(); **
** self.root.as_mut().map(|root| deque.push_front(root.iter_mut())); **
** IterMut(deque) **
** } **
**} **
**impl
** pub fn iter_mut(&mut self) -> NodeIterMut
** NodeIterMut { **
** elem: Some(&mut self.elem), **
** left: self.left.as_mut().map(|node| &mut **node), **
** right: self.right.as_mut().map(|node| &mut **node), **
** } **
** } **
**} **
**impl<'a, T> Iterator for NodeIterMut<'a, T> { **
** type Item = State<'a, T>; **
** fn next(&mut self) -> OptionSelf::Item { **
** match self.left.take() { **
** Some(node) => Some(State::Node(node)), **
** None => match self.elem.take() { **
** Some(elem) => Some(State::Elem(elem)), **
** None => match self.right.take() { **
** Some(node) => Some(State::Node(node)), **
** None => None, **
** } **
** } **
** } **
** } **
**} **
**impl<'a, T> DoubleEndedIterator for NodeIterMut<'a, T> { **
** fn next_back(&mut self) -> OptionSelf::Item { **
** match self.right.take() { **
** Some(node) => Some(State::Node(node)), **
** None => match self.elem.take() { **
** Some(elem) => Some(State::Elem(elem)), **
** None => match self.left.take() { **
** Some(node) => Some(State::Node(node)), **
** None => None, **
** } **
** } **
** } **
** } **
**} **
**impl<'a, T> Iterator for IterMut<'a, T> { **
** type Item = &'a mut T; **
** fn next(&mut self) -> OptionSelf::Item { **
** loop { **
** match self.0.front_mut().and_then(|node_it| node_it.next()) { **
** Some(State::Elem(elem)) => return Some(elem), **
** Some(State::Node(node)) => self.0.push_front(node.iter_mut()), **
** None => if let None = self.0.pop_front() { **
** return None **
** }, **
** } **
** } **
** } **
**} **
**impl<'a, T> DoubleEndedIterator for IterMut<'a, T> { **
** fn next_back(&mut self) -> OptionSelf::Item { **
** loop { **
** match self.0.back_mut().and_then(|node_it| node_it.next_back()) { **
** Some(State::Elem(elem)) => return Some(elem), **
** Some(State::Node(node)) => self.0.push_back(node.iter_mut()), **
** None => if let None = self.0.pop_back() { **
** return None **
** }, **
** } **
** } **
** } **
**} **
위 코드들은 모두 완전히 safe하고 stable rust에서 잘 동작해요!
이건 궁극적으로 이전에 보았던 단순한 구조체 케이스에서 빠져나왔습니다.
Rust는 하위 필드로 가변 참조를 안전하게 분할할 수 있음을 이해합니다.
그런 다음 Option을 통해 참조를 영구적으로 사용하도록 인코딩할 수 있습니다.
또는 슬라이스의 경우 빈 슬라이스로 대체하는 방법을 쓸 수도 있죠.