Закрепление (pinning)
Чтобы опросить futures
, они должны быть закреплены с помощью специального типа под названием
Pin<T>
. Если Вы прочитаете описание типажа Future
в
предыдущем разделе "выполнение Futures
и задач", вы узнаете о
Pin
из self: Pin<&mut Self>
в методеFuture:poll
.
Но что это значит, и зачем нам это нужно?
Для чего перемещение
Закрепление даёт гарантию того, что объект не будет перемещён.
Чтобы понять почему это важно, нам надо помнить как работает async
/.await
.
Рассмотрим следующий код:
# #![allow(unused_variables)] #fn main() { let fut_one = ...; let fut_two = ...; async move { fut_one.await; fut_two.await; } #}
Под капотом, он создаёт два анонимных типа, которые реализуют типаж Future
,
предоставляющий метод poll
, выглядящий примерно так:
# #![allow(unused_variables)] #fn main() { // Тип `Future`, созданный нашим `async { ... }` блоком struct AsyncFuture { fut_one: FutOne, fut_two: FutTwo, state: State, } // Список возможных состояний нашего `async` блока enum State { AwaitingFutOne, AwaitingFutTwo, Done, } impl Future for AsyncFuture { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { loop { match self.state { State::AwaitingFutOne => match self.fut_one.poll(..) { Poll::Ready(()) => self.state = State::AwaitingFutTwo, Poll::Pending => return Poll::Pending, } State::AwaitingFutTwo => match self.fut_two.poll(..) { Poll::Ready(()) => self.state = State::Done, Poll::Pending => return Poll::Pending, } State::Done => return Poll::Ready(()), } } } } #}
Когда poll
вызывается первый раз, он опрашивает
fut_one
. Если fut_one
не завершена,
возвращается AsyncFuture::poll
. Следующие вызовы
poll
будут начинаться там, где завершился
предыдущий вызов. Этот процесс продолжается до тех пор, пока
future
не сможет завершиться.
Однако, что будет, если async
блок использует ссылки?
Например:
# #![allow(unused_variables)] #fn main() { async { let mut x = [0; 128]; let read_into_buf_fut = read_into_buf(&mut x); read_into_buf_fut.await; println!("{:?}", x); } #}
Во что скомпилируется эта структура?
# #![allow(unused_variables)] #fn main() { struct ReadIntoBuf<'a> { buf: &'a mut [u8], // указывает на `x` далее } struct AsyncFuture { x: [u8; 128], read_into_buf_fut: ReadIntoBuf<'?>, // какое тут время жизни? } #}
Здесь future
ReadIntoBuf
содержит ссылку на другое
поле нашей структуры, x
. Однако, если
AsyncFuture
будет перемещена, положение
x
тоже будет изменено, что сделает указатель,
сохранённый в read_into_buf_fut.buf
, недействительным.
Закрепление future в определённом месте памяти, предотвращает
эту проблему, делая безопасным создание ссылок на данные за
пределами async
блока.
Как использовать закрепление
Тип Pin
оборачивает указатель на другие типы,
гарантируя, что значение за указателем не будет перемещено.
Например, Pin<&mut T>
, Pin<&T>
,
Pin<Box<T>>
- все гарантируют, что положение
T
останется неизменным.
У большинства типов нет проблем с перемещением. Эти типы
реализуют типаж Unpin
. Указатели на
Unpin
-типы могут свободно помещаться в
Pin
или извлекаться из него. Например, тип
u8
реализует Unpin
, таким образом
Pin<&mut T>
ведёт себя также, как и
&mut T
.
Некоторые функции требуют, чтобы future
, с которыми они
работают, были Unpin
. Для использования
Future
или Stream
, не реализующего
Unpin
, с функцией, требующей
Unpin
-типа, вы сначала должны закрепить значение
при помощи Box::pin
(для создания
Pin<Box<T>>
) или макроса
pin_utils::pin_mut!
(для создания
Pin<&mut T>
). Оба Pin<Box<Fut>>
и
Pin<&mut Fut>
могут использоваться как future
, и
оба реализуют Unpin
.
Например:
# #![allow(unused_variables)] #fn main() { use pin_utils::pin_mut; // `pin_utils` - удобный пакет, доступный на crates.io // Функция, принимающая `Future`, которая реализует `Unpin`. fn execute_unpin_future(x: impl Future<Output = ()> + Unpin) { ... } let fut = async { ... }; execute_unpin_future(fut); // Ошибка: `fut` не реализует типаж `Unpin` // Закрепление с помощью `Box`: let fut = async { ... }; let fut = Box::pin(fut); execute_unpin_future(fut); // OK // Закрепление с помощью `pin_mut!`: let fut = async { ... }; pin_mut!(fut); execute_unpin_future(fut); // OK #}