[Rust] 비동기 러스트: Future (번역)

https://rust-lang.github.io/async-book/02_execution/02_future.html

Future 트레잇은 러스트에서의 비동기 프로그래밍에서 중심을 차지하는 녀석입니다.

Future는 값을 산출할 수 있는 비동기적 계산도구에요. () 등으로 값이 비어있는 경우에도 잘 동작하죠.

future 트레잇의 간단한 버전은 이렇게 표현할 수 있습니다.

**trait SimpleFuture **
**{ **
**    type Output; **
**    fn poll(&mut self, wake: fn()) -> PollSelf::Output; **
**} **

**enum Poll **
**{ **
**    Ready(T), **
**    Pending, **
**} **

Future는 poll 메서드를 호출하는 것으로 상태가 진행될 수 있는데요. 이건 future가 최대한 완료되도록 유도할 겁니다.

future가 완료되면 poll 메서드는 Poll::Ready(result)를 반환할 것이고.
아직 완료되지 않았다면 Poll::Pending 반환하고 Future가 더 진행될 준비가 되었을 때 wake() 함수가 호출되도록 정렬합니다.
->If the future is not able to complete yet, it returns Poll::Pending and arranges for the wake() function to be called when the Future is ready to make more progress.

wake()가 호출되면, executor는 Future가 poll을 다시 호출하도록 유도합니다. Future가 더 진행될 수 있게끔요.

wake()가 없다면, executor는 특정 future가 언제 진행됐는지를 알 방법이 없고, 모든 future를 지속적으로 polling해야만 할겁니다.
wake()가 있으면 future들이 polling할 준비가 되었는지를 정확하게 알 수 있고요.

가령, 데이터를 사용할 수 있거나 없는 소켓에서 데이터를 읽어올 경우를 생각해봅시다.
데이터가 있다면 그냥 읽어서 Poll::Ready(data)을 반환하면 됩니다. 하지만 준비된 데이터가 없다면 future는 블럭되고 더이상의 진전이 없을 거에요.

사용가능한 데이터가 없을 경우엔, 데이터가 소켓에 준비될 때 호출될 wake를 등록해야만 합니다. 그건 executor에게 future가 진행될 준비가 됐는지를 말해줄 거에요.

여기에 간단한 SocketRead future 코드가 있습니다.

**pub struct SocketRead<'a> **
**{ **
**    socket: &'a Socket, **
**} **

**impl SimpleFuture for SocketRead<'_> **
**{ **
**    type Output = Vec; **
**    fn poll(&mut self, wake: fn()) -> PollSelf::Output **
**    { **
**        if self.socket.has_data_to_read() **
**        { **
**            // 소켓에 데이터가 있음. **
**            // 버퍼에서 읽어서 그것을 반환함
.
**            Poll::Ready(self.socket.read_buf **
**        } **
**        else **
**        { **
**            // 소켓에 아직 데이터가 없음.
**            // **
**            // 데이터가 사용가능해지면 wake가 호출됨

**            // 그리고 이 Future의 사용자는 다시 poll을 호출.

**            // 데이터를 받아옴

**            self.socket.
set_readable_callback
(wake); **
**            Poll::Pending **
**        } **
**    } **
**} **

Future의 이러한 모델은 중간 할당(intermediate allocations) 없이도 여러개의 비동기 연산과 함께 구성될 수 있습니다.

여러개의 future를 한번에 실행하거나 함께 묶는(chain) 것은 아래와 같은 allocation-free 상태머신으로 구현될 수 있고요.

/// 2개의 다른 future를 동시에 실행하는 SimpleFuture
/// **
/// 병행성은 각 future의 poll을 호출이
/// interleave될 수 있다는 사실을 통해 달성됩니다.
/// 그리고 각 future는 각자의 속도로 진행될 수 있습니다.
pub struct Join<FutureA, FutureB> **
{ **
**   **** // 각 필드는 실행될 future를 포함합니다.

**    // future가 이미 완료됐다면, 그 필드는 None이 될거에요.

**    // 이건 future가 완료된 이후에도

**    // future를 polling하는 것을 방지합니다.**
**    // 그건 Future 트레잇의 규칙을 위반하는 거니까요.**
**    a: Option, **
**    b: Option, **
**} **

**impl<FutureA, FutureB> SimpleFuture for Join<FutureA, FutureB> **
**where **
**FutureA: SimpleFuture<Output = ()>, **
**FutureB: SimpleFuture<Output = ()>, **
**{ **
**    type Output = (); **
**    fn poll(&mut self, wake: fn()) -> PollSelf::Output **
**    { **
**      ****  // future a 완료 시도. **
**        if let Some(a) = &mut self.a **
**        { **
**            if let Poll::Ready(()) = a.poll(wake) **
**            { **
**                self.a.take(); **
**            } **
**        } **
**        // future b 완료 시도. **
**        if let Some(b) = &mut self.b **
**        { **
**            if let Poll::Ready(()) = b.poll(wake) **
**            { **
**                self.b.take(); **
**            } **
**        } **
**        if self.a.is_none() && self.b.is_none() **
**        { **
**           **** // future 둘다 완료됨

**            // 성공적으로 반환할 수 있음

**            Poll::Ready(()) **
**        } **
**        else **
**        { **
**            **// future 하나 이상이 Poll::Pending을 반환하고, 여전히 작동중에 있음.
// 진척이 있으면 wake()를 호출/
**            Poll::Pending **
**         } **
**    } **
**} **

이건 여러개의 future들이 어떻게 별도의 할당 없이 동시에 실행될 수 있는지를 보여주고, 더 효율적인 비동기 프로그램을 만들게 해줍니다.

비슷하게, 여러개의 연속된 future들은 연달아 실행되게 할 수 있습니다. 이렇게요.

/// 뒤이어 이후에 2개의 future를 실행하는SimpleFuture**.**
// **
// 이 예제의 목적을 위해, AndThenFut이 **
// first와 second future 둘다 생성 시에 바로
// 사용이 가능하다고 가정합니다.
// 실제 AndThen combinator는 **
// first future이 출력값에 기반해서 **
// second future를 생성하게 합니다.
// 이것처럼요.
get_breakfast.and_then(|food| eat(food))

**pub struct AndThenFut<FutureA, FutureB> **
**{ **
**    first: Option, **
**    second: FutureB, **
**} **

**impl<FutureA, FutureB> SimpleFuture for AndThenFut<FutureA, FutureB> **
**where **
**FutureA: SimpleFuture<Output = ()>, **
**FutureB: SimpleFuture<Output = ()>, **
{ **
**    type Output = (); **
**    fn poll(&mut self, wake: fn()) -> PollSelf::Output **
**    { **
**        if let Some(first) = &mut self.first **
**        { **
**            match first.poll(wake) **
**            { **
**                // first future가 완료됨.
**                // 제거하고 second 시작!

**                Poll::Ready(()) => self.first.take(), **
**               **** // 아직 first future가 완료되지 않음

**                Poll::Pending => return Poll::Pending, **
**              }; **
**        } **
**        **// 이제 first future가 완료됨.
**        // second 완료를 시도.          **** **
**        self.second.poll(wake) **
**    } **
**} **

These examples show how the Future trait can be used to express asynchronous control flow without requiring multiple allocated objects and deeply nested callbacks.
이 예제들은 Future 트레잇이 중복할당된 객체와 깊게 중복된 콜백 없이 비동기 제어흐름을 표현하는 방법을 보여줍니다.

이번엔 이런 기본적인 제어흐름에서 벗어나서 실제 Future 트레잇은 무엇이 다른지를 이야기해보죠.

**trait Future **
**{ **
**    type Output; **
**    fn poll( **
**        ****// &mut self에서 ******Pin<&mut Self>로 바꿈
**        self: Pin<&mut Self>, **
**        // wake: fn()는 **cx:
&mut Context<'_>로 바꿈

**        cx: &mut Context<'_>, **
**      ) -> PollSelf::Output; **
**} **

첫번째 인자를 보죠. self 타입으로 더이상 &mut self를 쓰지 않고 Pin<&mut self>로 바뀌었습니다.
Pinning에 대한 자세한 이야기는 해당 챕터에서 다룰텐데요. 일단은 이동이 불가능한 Future를 생성하게 해주는 거라고만 알아둡시다.

이동불가능한 객체들은 그 필드 사이에 포인터를 저장할 수 있게 됩니다. 이렇게요.
struct MyFut { a: i32, ptr_to_a: *const i32 }.

Pinning은 async/await에 있어서는 필수적인 녀석입니다.

두번째로, wake: fn()는 &mut Context<'_>로 바뀌었는데요.
SimpleFuture에서 우리는 함수포인터 fn()로의 호출을 사용해서 future executor에게 해당 future를 polling해야함을 말해줬습니다.
하지만 fn()는 그냥 함수포인터일 뿐이라, wake를 호출한 Future에 대해 그 어떤 데이터도 저장할 수 없어요.

현실의 시나리오에서 웹서버 같은 복잡한 애플리케이션은 wakeup이 따로 관리되어야 하는 수천개의 서로 다른 연결이 있을 수 있습니다.

Context 타입은 이러한 문제를 Waker 타입 값의 접근을 제공하는 것으로 해결합니다. Waker는 지정된 task를 wake up할 수 있게 해주죠.
->The Context type solves this by providing access to a value of type Waker, which can be used to wake up a specific task.