Future Trait

Future trait은 Rust의 비동기 프로그래밍의 중심에 있습니다. Future는 값을 생성 할 수있는 비동기 연산입니다. (이 값은 비어있을 수 있습니다. 예 :()). simplified 버전의 future trait은 다음과 같습니다.


#![allow(unused)]
fn main() {
trait SimpleFuture {
    type Output;
    fn poll(&mut self, wake: fn()) -> Poll<Self::Output>;
}

enum Poll<T> {
    Ready(T),
    Pending,
}
}

poll 함수를 호출하여 Future을 실행할 수 있습니다. 이러한 호출은 future를 가능한 한 완수를 목표로 합니다. Future가 완료되면 Poll::Ready(result)를 반환합니다. Future가 아직 완료되지 않았으면 Poll::Pending을 반환하고 Future가 더 진행 할 준비가 되었을 때wake()함수가 호출되도록 해 주어야 합니다. wake()가 호출되면 Future를 실행하는 executor는 Future를 다시 호출하여 Future가 중단되었던 지점에서 실행을 계속 하도록 합니다.

wake()가 없으면 executor는 특정 future가 언제 다시 실행 가능한 지를 알 방법이 없으므로, 항상 모든 future를 polling 해야 합니다. wake()를 사용하면, executor는 어떤 future가 실행 가능한지를 polling 해야 하는지 정확히 알 수 있습니다.

예를 들어, 사용 가능한 데이터가 있을 수도, 없을 수도 있는 소켓을 읽는 경우를 고려하십시오. 데이터가 있으면 읽을 수 있고 Poll::Ready(data)를 반환하지만 준비된 데이터가 없으면 future는 block되며 더 이상 진행할 수 없습니다. 사용 가능한 데이터가 없으면 소켓에서 데이터가 준비되면 호출 될 wake를 등록해야 합니다. 그것은 우리의 future가 재실행 할 준비가 되었다고 ezecutor에게 알려줄 것입니다. 간단한 SocketRead future는 다음과 같습니다.

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

impl SimpleFuture for SocketRead<'_> {
    type Output = Vec<u8>;

    fn poll(&mut self, wake: fn()) -> Poll<Self::Output> {
        if self.socket.has_data_to_read() {
            // The socket has data-- read it into a buffer and return it.
            Poll::Ready(self.socket.read_buf())
        } else {
            // The socket does not yet have data.
            //
            // Arrange for `wake` to be called once data is available.
            // When data becomes available, `wake` will be called, and the
            // user of this `Future` will know to call `poll` again and
            // receive data.
            self.socket.set_readable_callback(wake);
            Poll::Pending
        }
    }
}

Future 모델은 중간 할당이 필요없는 여러 개의 비동기 동작을 하는 여러 future를 구성 할 수 있습니다. 한 번에 여러 개의 futures, 또는 chained futures를 allocation-free state machines을 사용하여 실행되는 코드를 구현할 수 있습니다.

/// A SimpleFuture that runs two other futures to completion concurrently.
///
/// Concurrency is achieved via the fact that calls to `poll` each future
/// may be interleaved, allowing each future to advance itself at its own pace.
pub struct Join<FutureA, FutureB> {
    // Each field may contain a future that should be run to completion.
    // If the future has already completed, the field is set to `None`.
    // This prevents us from polling a future after it has completed, which
    // would violate the contract of the `Future` trait.
    a: Option<FutureA>,
    b: Option<FutureB>,
}

impl<FutureA, FutureB> SimpleFuture for Join<FutureA, FutureB>
where
    FutureA: SimpleFuture<Output = ()>,
    FutureB: SimpleFuture<Output = ()>,
{
    type Output = ();
    fn poll(&mut self, wake: fn()) -> Poll<Self::Output> {
        // Attempt to complete future `a`.
        if let Some(a) = &mut self.a {
            if let Poll::Ready(()) = a.poll(wake) {
                self.a.take();
            }
        }

        // Attempt to complete 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() {
            // Both futures have completed-- we can return successfully
            Poll::Ready(())
        } else {
            // One or both futures returned `Poll::Pending` and still have
            // work to do. They will call `wake()` when progress can be made.
            Poll::Pending
        }
    }
}

이것은 여러 futures를 독립적인 각각의 할당 없이 동시에 실행할 수 있는 방법을 보여 줍니다. 이러한 기능은 보다 효율적인 비동기식 프로그램을 허용합니다. 마찬가지로 다음과 같이 여러 순차 futures을 차례로 실행할 수도 있습니다.

/// A SimpleFuture that runs two futures to completion, one after another.
//
// Note: for the purposes of this simple example, `AndThenFut` assumes both
// the first and second futures are available at creation-time. The real
// `AndThen` combinator allows creating the second future based on the output
// of the first future, like `get_breakfast.and_then(|food| eat(food))`.
pub struct AndThenFut<FutureA, FutureB> {
    first: Option<FutureA>,
    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()) -> Poll<Self::Output> {
        if let Some(first) = &mut self.first {
            match first.poll(wake) {
                // We've completed the first future-- remove it and start on
                // the second!
                Poll::Ready(()) => self.first.take(),
                // We couldn't yet complete the first future.
                Poll::Pending => return Poll::Pending,
            };
        }
        // Now that the first future is done, attempt to complete the second.
        self.second.poll(wake)
    }
}

이 예는 Future trait을 사용하여 여러 개의 할당된 객체와 깊이 중첩된 콜백 없이 비동기 제어를 표현하는 방법을 보여줍니다. 기본적인 제어 흐름에 대해 설명하였으니. 실제 Future trait과 그것의 차별성을 살펴보겠습니다.

trait Future {
    type Output;
    fn poll(
        // Note the change from `&mut self` to `Pin<&mut Self>`:
        self: Pin<&mut Self>,
        // and the change from `wake: fn()` to `cx: &mut Context<'_>`:
        cx: &mut Context<'_>,
    ) -> Poll<Self::Output>;
}

가장 먼저 눈에 띄는 변화는 self type이 더 이상 &mut self가 아니라는 것입니다. 이 것은 Pin<&mut Self>로 변경 되었습니다. 나중에 Pin에 대해 자세히 설명하겠습니다pinning. 지금은 우리가 만드는 future가 재배치 될 수 없다는 것 정도로 이해하고 넘어 갑시다. 재배치 될 수 없는 객체는 필드 사이에 포인터를 저장할 수 있습니다. 예 : struct MyFut { a: i32, ptr_to_a: * const i32 }. Pinning은 async/await를 활성화 하는데 필요합니다.

둘째, wake: fn()&mut Context<'_>로 변경되었습니다. SimpleFuture에서 우리는 함수 포인터 (fn())을 호출하여 future의 executor에게 해당 future는 poll되어야 한다고 알려 주었습니다. 그러나 fn()은 함수 포인터일 뿐이고 이를 사용하면 future에 대한 호출과 관련된 데이터를 저장할 수 없습니다.

실제 시나리오에서, 웹 서버와 같은 복잡한 응용 프로그램은 수천 개의 서로 다른 커넥션과 관련된 웨이크 업이 모두 별도로 관리 되어야 됩니다. Context type은 특정 작업을 깨우는 데 사용할 수있는 Waker type의 값에 접근하도록 해 주어서 이 문제를 해결 합니다.