Pin과 Unpin
포스트
취소

Pin과 Unpin

개요

PinPin<T>에서 (T는 포인터) T가 가리키는 내용이 Unpin을 구현하지 않는 한 T가 이동되지 않도록 보장하는 스마트 포인터입니다.

T는 이동(move)할 수 있는 포인터만 유효합니다. 만약 TUnpin을 구현하고 있다면, Pin이 비활성화 됩니다.
대부분 Unpin은 자동으로 구현됩니다. 물론 PhantomPinned 같은 경우엔 예외입니다.
Pin을 설명하기 전에, 이동에 대해 잠시 보도록 합시다:

1
2
3
4
5
fn main() {
    let s = String::from("hello");
    let s1 = s; // 이 시점에서 s는 유효하지 않습니다.
    println!("{}", s1);
}

이동(move)은 우리와 너무나도 친숙한 존재일겁니다.

이동은 러스트를 배우지 않았다면 불편한 존재지만, 러스트를 배우고 다시 보면 러스트의 자랑스러운 기능 중 하나 일 것입니다.
하지만 이런 이동이 일어나지 않아야 할 상황이 있습니다. 바로 자기 참조 구조(self reference structure)입니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use std::ptr;

struct Foo {
    foo: usize,
    bar: *mut usize,
}

impl Foo {
    pub fn new(foo: usize) -> Foo {
        Foo { foo, bar: ptr::null_mut() }
    }

    pub fn init(&mut self) {
        self.bar = &mut self.foo;
    }

    pub unsafe fn get_mut(&self) -> &mut usize {
        &mut *self.bar
    }
}

fn main() {
    let mut foo = Foo::new(1);
    foo.init();

    let foo_mut = unsafe { foo.get_mut() };

    *foo_mut = 5;
    println!("{}", foo_mut);
}

보통 이런 모습을 자기 참조 구조라 부릅니다.
위 코드에서 get_mut()foo의 참조가 아닌 bar에 저장해둔 참조를 반환합니다.
이 코드는 잘 작동하지만, 조금만 수정해봅시다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 생략

fn foo() -> Foo {
    let mut foo = Foo::new(1);
    foo.init();
    foo
}

fn main() {
    let f = foo();

    let foo_mut = unsafe { f.get_mut() };

    *foo_mut = 5;
    println!("{}", foo_mut);
}

이걸 실행해보면 이상한 값이 나오는데, barfoo 함수에서의 스택을 가리키고 있기 때문에, Undefined Behavior (UB) 가 발생합니다. 이미 해제된 메모리를 역참조하였기 때문이죠.

예를 들어, 어떤 객체가 0x0001에 위치해있고, 그 객체를 가리키는 포인터가 있다고 가정합시다.
만약 그 객체가 재배치 되어, 임의의 위치 0x0002로 이동하였다면 상당히 곤란하겠죠.
이 문제의 해결 방법은 이동해도 변하지 않는 주소일 때만 참조를 저장해야 합니다.

Pin은 그 문제를 해결해줍니다:

Pin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
use std::{marker::*, pin::*, ptr};

struct Foo {
    foo: usize,
    bar: *mut usize,
    _pin: PhantomPinned,
}

impl Foo {
    pub fn new(foo: usize) -> Foo {
        Foo { foo, bar: ptr::null_mut(), _pin: PhantomPinned }
    }

    pub fn init(self: Pin<&mut Self>) {
        unsafe {
            let this = self.get_unchecked_mut();
            this.bar = &mut this.foo;
        }
    }

    pub fn get_foo_mut(self: Pin<&mut Self>) -> Option<&mut usize> {
        if self.bar.is_null() {
            None
        } else {
            unsafe { Some(&mut *self.bar) }
        }
    }
}

fn foo() -> Pin<Box<Foo>> {
    let foo = Foo::new(1);
    let mut foo = Box::pin(foo);

    foo.as_mut().init();
    foo
}

fn main() {
    let mut f = foo();

    let foo_mut = f.as_mut().get_foo_mut();

    if let Some(foo_mut) = foo_mut {
        *foo_mut = 5;

        println!("{}", *foo_mut);
    }
}

여기에서 Foo 구조체의 _pin 필드가 왜 존재하는지 의문일 수 있습니다.

marker 타입 PhantomPinned를 사용하여 Foo!Unpin으로 만들어줍니다.
(Send 처럼 !Send를 구현할 수 있도록 하는 기능은 아직 지원하지 않습니다.)

Box<T> 또한 Unpin이기 때문에, Pin<Box<T>>으로 래핑 하였습니다.
Pin을 사용한 타입들은 항상 고정된 주소를 가지게 되었고, barPin을 사용하여 초기화 되었습니다.
그렇기에 Pin<Box<Foo>>Pin<&mut Foo> (as_mut)로 만드는 건 안전합니다.

추가적으로 Pin<T>을 사용할 때 조건이 있습니다:

  • TUnpin이면, TPin에 의해 소멸될 때까지 TUnpin 상태로 유지해야 합니다.
  • T!Unpin이면, TPin에 의해 소멸될 때까지 고정됩니다.

Unpin을 구현하면 Pin&mut T를 안전한 러스트에서 허용해주며, 그렇지 않으면 안전한 러스트에서 고정할 수 있습니다. (즉 &mut T를 얻을 수 없음).

때문에 아래의 코드는 작동하지 않습니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use std::{mem::*, pin::*};

fn swap_data<T>(x: Pin<&mut T>, y: Pin<&mut T>) {
    swap(&mut *x, &mut *y); // <- cannot borrow data in dereference of `std::pin::Pin<&mut T>` as mutable
}

fn main() {
    let mut x = 5;
    let mut y = 10;
    let x = Pin::new(&mut x);
    let y = Pin::new(&mut y);

    swap_data(x, y);
}

그럼 이런걸 어디에 쓸까요? 이는 Future에서 사용하는데 (사실상 여기서만 사용), Future은 이 글에서 자세히 다루진 않으나, 비동기에서 사용하는 중요한 개념입니다.