1P by neo 2달전 | favorite | 댓글 1개

Pin

  • Pin 타입과 pinning 개념은 Rust 비동기 생태계의 기초적인 구성 요소임
  • 그러나 Pin은 접근하기 어렵고 오해받기 쉬운 요소 중 하나임
  • 이 글은 Pin이 무엇을 달성하는지, 어떻게 생겨났는지, 그리고 현재 Pin의 문제점이 무엇인지 설명함

Requirements

  • 비동기 함수에서 참조를 지원하기 위해 Future 내부에 참조를 저장해야 했음
  • 문제는 이러한 참조가 자기 참조 일 수 있다는 것임
  • 예시 코드:
    async fn foo<'a>(z: &'a mut i32) { ... }
    async fn bar(x: i32, y: i32) -> i32 {
        let mut z = x + y;
        foo(&mut z).await;
        z
    }
    
  • Bar의 내부 상태는 다음과 같음:
    enum Bar {
        Start { x: i32, y: i32 },
        FirstAwait { z: i32, foo: Foo<'?> },
        Complete,
    }
    
  • Pin의 목표는 안전하게 자기 참조 타입을 조작하는 것임

Non-solutions: move constructors and offset pointers

  • 이동 생성자와 오프셋 포인터는 Rust에서 작동하지 않음
  • 이동 생성자는 이동 시 포인터를 수정하지만, Rust에서는 불가능함
  • 오프셋 포인터는 컴파일 시 참조가 자기 참조인지 아닌지 알 수 없기 때문에 작동하지 않음

The “pinned typestate”

  • 객체가 항상 이동 불가능한 것이 아니라 특정 시점부터 이동 불가능해야 함
  • Ralf Jung의 모델에서는 객체가 "소유됨" 상태에서 "공유됨" 상태로, 그리고 "고정됨" 상태로 전환됨
  • 고정된 상태에 들어가면 객체는 더 이상 이동할 수 없음

?Move

  • Pin 이전에 Move라는 새로운 트레이트를 기반으로 한 솔루션을 시도했음
  • Move를 구현하지 않는 타입은 참조를 취할 때 고정 상태로 전환됨
  • 그러나 Move는 역호환성을 제공하지 않음

Pin

  • Pin은 객체를 고정 상태로 만드는 새로운 참조 유형을 설계함
  • Pin은 라이브러리 API로 구현되어 역호환성을 유지함
  • Unpin 자동 트레이트를 추가하여 대부분의 타입이 고정 상태와 일반 상태를 구분하지 않도록 함

The problems with Pin

  • Pin은 사용성 측면에서 여러 문제를 가지고 있음
  • Pin은 라이브러리 타입으로 구현되어 일반 참조 타입이 가진 많은 기능이 사라짐
  • 예를 들어, &mut TCopy를 구현하지 않지만 여러 번 인자로 전달할 수 있음
  • Pin은 이러한 편의를 제공하지 않음
  • Pin을 사용할 때 많은 혼란이 발생함

In my next post…

  • Pin은 비동기 함수에서 임의의 참조를 안전하게 컴파일할 수 있게 해줌
  • 그러나 Pin은 복잡성을 증가시키며, 이를 개선할 방법을 다음 글에서 다룰 예정임

GN⁺의 정리

  • Pin은 Rust 비동기 생태계의 중요한 구성 요소임
  • Pin의 사용성 문제는 라이브러리 타입으로 구현되었기 때문임
  • Pin을 개선하기 위한 방법을 다음 글에서 다룰 예정임
  • 비슷한 기능을 가진 프로젝트로는 pin-project-lite가 있음
Hacker News 의견
  • Pin이 이해하기 어려운 이유는 공식 문서에서 명확하게 설명되지 않기 때문임

    • 문서에서 "Pin은 객체가 절대 이동하지 않도록 보장한다"고 주장하지만, 이는 사실이 아님
    • 대부분의 일반 객체는 Unpin이므로 Pin은 보통 아무런 역할을 하지 않음
    • Pin이 실제로 작동하는 타입 T의 집합은 매우 특이하고 문서에서 충분히 강조되지 않음
  • Pin이 어려운 이유는 자체적으로 의미가 없기 때문임

    • Pin<&mut InnerType>의 경우, 언어나 표준 라이브러리에서 Pin이 무엇을 할 수 있고 할 수 없는지 알려주지 않음
    • 대신, InnerType 제공자가 추가적인 (내부적으로 안전하지 않은) 메서드와 API를 만들어 핀된 객체를 조작할 수 있게 함
    • Pin<P> 자체의 유일한 목적은 더 적은 "내재된 기능"을 제공하는 포인터를 제공하는 것임
  • 제목에 "rust"를 추가해야 기사 내용이 무엇인지 알 수 있음

  • "value identity"라는 용어는 Mojo의 문서 어디에서도 정의되지 않았음

    • Dave Abrahams의 "Value Semantics: Safety, Independence, Projection, & Future of Programming" 강연을 추천함
  • Pin은 기술적으로 정확하지만 이해하기 어려운 이름의 좋은 예임

    • "Drop"은 더 익숙한 의미를 가지지만, "pinning"은 그렇지 않음
    • "immovable!(…)"이 더 나을지 모르겠지만, 더 나은 이름을 생각하기 어려움
    • "prevent_moving!(…)"과 같은 설명적인 이름과 PreventMove 트레이트가 더 나을 수 있음
  • Rust와 유사한 언어에서 move-constructors가 있다면 Pin의 필요성이 사라질 수 있음

    • 사용자가 객체를 파괴할 방법이 없기 때문에 이동할 방법도 없을 것임
  • &mut 참조를 통해 mem::swap/replace로 객체를 이동할 수 있지만, 실제로 필요한 경우는 드묾

    • 이동-참조를 선택할 수 있는 방법이 있었으면 좋겠음
    • swap과 replace를 안전하지 않게 만드는 것이 문제를 해결할 수 있을 것임
  • WithoutBoats는 비동기 반복자, poll, pin 주제에 대해 활발한 토론을 진행 중임

    • 언어의 세부 사항에 대해 공개적으로 깊이 논의하는 커뮤니티가 거의 없으며, 이를 보는 것이 매우 흥미로움
  • Pinning/!Move는 비동기/대기 외에도 많은 용도로 유용함

    • 하지만 Rust가 이를 제대로 처리하지 못해, 보통의 대답은 "프로그램을 다른 언어로 다시 작성하라"임