2017-11-06 19 views
6

지퍼처럼 구현하려고하지만 변경 가능한 참조를 활용하여 데이터 구조를 분해하고 재구성하지 않아도됩니다. 연결된 목록을 사용하기위한 예제 코드가 있지만, 나무와 같은 다른 구조에 적용하는 것이 이상적입니다.에일리어스가 적용된 후에도 변경 가능한 참조 저장

pub enum List<T> { 
    Empty, 
    Cons { head: T, tail: Box<List<T>> }, 
} 

pub struct Zipper<'a, T: 'a> { 
    trail: Option<Box<Zipper<'a, T>>>, 
    focus: &'a mut List<T>, 
} 

impl<'a, T: 'a> Zipper<'a, T> { 
    pub fn down(&'a mut self) { 
     match self.focus { 
      &mut List::Empty =>(), 
      &mut List::Cons { 
       tail: ref mut xs, .. 
      } => { 
       //We need a way to convince rust that we won't use oldZipper 
       //until xs goes out of scope 
       let oldZipper = std::mem::replace(
        self, 
        Zipper { 
         trail: None, 
         focus: xs, 
        }, 
       ); 
       self.trail = Some(Box::new(oldZipper)); 
      } 
     } 
    } 
} 

차용 검사기이 함께 행복하지 않다 :

error[E0499]: cannot borrow `*self` as mutable more than once at a time 
    --> src/main.rs:21:21 
    | 
16 |     tail: ref mut xs, .. 
    |      ---------- first mutable borrow occurs here 
... 
21 |      self, 
    |      ^^^^ second mutable borrow occurs here 
... 
30 |  } 
    |  - first borrow ends here 

이것은 놀라운 일이 아니다 : 우리는 지퍼가리스트에 초점을 맞추고 그것에 down를 호출 할 경우, 우리는 변경 가능한 지퍼를 얻을 수 그 목록의 꼬리를 참조하십시오. 그래서 우리는 앨리어스를 변경할 수 있습니다.

그러나 지퍼의 trail을 사용하지 않고 focus이 범위를 벗어나지 않으면 변경 가능한 앨리어싱을 "볼"수 없습니다. 이것은 정상적인 변경 가능한 차용과 유사합니다. 차용이 범위를 벗어날 때까지 빌린 변수를 사용할 수 없습니다.

차용 검사기에 설명 할 수있는 방법이 있습니까? 배열에서 두 개의 겹쳐지지 않는 조각을 빌려 오는 것이 차용 검사기에 "설명"하고 싶다면 split_at을 사용할 수 있습니다. trail이 사용되지 않는 일부 기능이 있습니까? focus이 범위를 벗어나고 이렇게하면 빌려가는 사람을 만족시킬 수 있습니까?

+0

[지퍼 구현] (https://stackoverflow.com/a/36168919/155423)이 질문에 대한 답변을 제공합니까? – Shepmaster

+0

아닙니다. 연결 한 것과 같은 haskell 스타일의 지퍼를 구현하는 것은 비교적 간단합니다. 나는 또한 당신이 내려갈 때 데이터 구조를 분해하는 것을 피하려고 노력하고있다. 따라서 당신이 상승 할 때 그것을 재구성 할 필요가있다. – user1454156

+3

대답의 예는 다음과 같습니다. "분석이 잘못되었습니다 : 안전하지 않은 코드를 사용하여 이것을 구현하면 UB가됩니다.", "분석은 정확하지만 불가능합니다. "귀하의 분석은 정확하지만 AFAIK는 훌륭한 도서관의 안전하지 않은 코드를 작성했습니다."또는 "귀하의 분석은 정확합니다. 여기 도서관은 – user1454156

답변

3

목표를 달성하려면 Zipper 구조체에서 변경 가능한 참조를 제거해야합니다. 우리는 가변적 인 원시 포인터를 대신 사용할 수 있습니다 : 그들은 우리가 그들의 지시 대상을 돌연변이하게 할 수 있습니다. 그리고 우리는 특정 객체를 가리키는 포인터가 둘 이상 존재할 수 있습니다. 그러나 역 참조는 안전하지 않습니다.

use std::mem; 
use std::marker::PhantomData; 

pub enum List<T> { 
    Empty, 
    Cons { head: T, tail: Box<List<T>> }, 
} 

pub struct Zipper<'a, T: 'a> { 
    trail: Option<Box<Zipper<'a, T>>>, 
    focus: *mut List<T>, 
    _list: PhantomData<&'a mut List<T>>, 
} 

impl<'a, T: 'a> Zipper<'a, T> { 
    pub fn new(list: &'a mut List<T>) -> Zipper<'a, T> { 
     Zipper { 
      trail: None, 
      focus: list as *mut List<T>, 
      _list: PhantomData, 
     } 
    } 

    pub fn down(&mut self) { 
     unsafe { 
      match *self.focus { 
       List::Empty =>(), 
       List::Cons { 
        tail: ref mut xs, .. 
       } => { 
        let old_zipper = mem::replace(
         self, 
         Zipper::new(xs), 
        ); 
        self.trail = Some(Box::new(old_zipper)); 
       } 
      } 
     } 
    } 
} 

fn main() { 
    let mut list = List::Cons { head: 1, tail: Box::new(List::Empty) }; 
    let mut zipper = Zipper::new(&mut list); 
    zipper.down(); 
    zipper.down(); 
} 

Zipper 구조체의 focus 필드가 지금 *mut List<T>입니다 :

여기에 코드입니다. 이것은 원시 포인터이기 때문에 자유롭게 복사 할 수 있습니다. 이것은 Zipper::down에있는 컴파일러 오류를 해결합니다. PhantomData<&'a mut List<T>> 유형의 새 필드 _list도 있습니다. PhantomData은 "필자가 아니더라도 컴파일러에게 T을 저장/소유하고있는 척"하는 특수한 유형입니다. 이 필드가 없으면 컴파일러에서 평생 매개 변수 'a이 사용되지 않는다고 불평합니다.

공지 사항 Zipper::new 것을 아직도 매개 변수로 &'a mut List<T> 예상 :이, 우리가 사용할 수있는 사실은 선언 할 List<T>에 고유 한 변경 가능한 참조를 가지고 호출을 요구함으로써 안전한 인터페이스를 제공하기 위해 Zipper을 허용하는 다른 안전하지 않은 작업에 구조체는 사용 가능한 변경 가능한 참조에 대한 충분한 지식을 갖고 있으므로 실제로 안전합니다. 컴파일러에 관한 한, Zipper이며, 변경 가능하게는 List을 빌립니다. List을 변경하려고 시도하는 동안 ListZipper이 범위에 있으면 List이 이미 변경 될 수 있다는 오류가 표시됩니다.


당신은 사용자가 Zipper의 초점에 대한 참조를 얻을 수 있도록하는 어떤 코드를 표시하지 않았습니다. 나는 안전하지 않을 가능성이있는 구현을 생각해 왔으며, 그 경로로가는 것이 유혹적이지만, 컴파일러는 잘못된 것이라고 말하지 않을 것이다.내가 당신을 보여 드리죠 : 그것이 우리가 주어진 무엇 때문에

impl<'a, T: 'a> Zipper<'a, T> { 
    pub fn focus(&mut self) -> &'a mut List<T> { 
     unsafe { &mut *self.focus } 
    } 
} 

그것은 &'a mut List<T>을 반환하는 유혹입니다. 그러나 반환 값의 수명이 self에 국한되지 않으므로 focus을 두 번 호출하여 동일한 List<T>에 대한 두 개의 변경 가능한 참조를 얻을 수 있음을 의미합니다. &'a mut List<T>Zipper 인 경우 컴파일러는 &'a mut List<T>을 반환하려고했는지 알려주지 만 (unsafe 코드를 사용하지 않는 한 컴파일러에서 알려줍니다). 원래 올바른 구현은 다음과 같습니다

이 구현에서
impl<'a, T: 'a> Zipper<'a, T> { 
    pub fn focus(&mut self) -> &mut List<T> { 
     unsafe { &mut *self.focus } 
    } 
} 

Zipper는 mutably 빌려 될만큼 &mut List<T>을 반환 된 &mut List<T>가 밖으로 때까지 우리가 focus (또는 down)를 호출 할 수 없음을 의미하는 약이다 범위.

+0

입니다. 일반'* mut "보다는"& UnsafeCell <> "을 사용할 이유가 있습니까? 내가 생각할 수있는 유일한 것은 'PhantomData'를 피하는 것입니다. 이것은 다른 사람들이 있는지 궁금합니다. –

+0

@MatthieuM. 'mut T '는 컴파일러에게'UnsafeCell'과 같은 일을 알려 줍니까? 'UnsafeCell'이 존재해야 할 이유가 * 있습니다. – Shepmaster

+1

@Shepmaster'UnsafeCell'은'U '(transitively) 뒤에있는'T'가 불변이라고 가정 할 수있는 규칙을 깨기 위해 존재합니다; '& UnsafeCell '뒤에'T '는 불변이라고 가정 할 수 없습니다. 여기서는'& mut T '로 시작하므로'UnsafeCell'을 사용하지 않아도됩니다. (처음에했던 것 (https://stackoverflow.com/posts/47143424/revisions)과는 반대로) –