Закрепление
Чтобы можно было опросить футуры, они должны быть закреплены с помощью специального типа Pin<T>
. Если вы прочитали объяснение типажа Future
в предыдущем разделе «Выполнение Future
и задач», вы узнаете Pin
из self: Pin<&mut Self>
в определении метода Future::poll
. Но что это значит, и зачем нам это нужно?
Для чего нужно закрепление
Pin
работает в тандеме с маркером Unpin
. Закрепление позволяет гарантировать, что объект, реализующий !Unpin
, никогда не будет перемещён. Чтобы понять, зачем это нужно, нужно вспомнить, как работает async
/.await
. Рассмотрим следующий код:
let fut_one = /* ... */;
let fut_two = /* ... */;
async move {
fut_one.await;
fut_two.await;
}
Под капотом создаётся анонимный тип, который реализует типаж Future
и метод poll
:
// Тип `Future`, созданный нашим `async { ... }`-блоком
struct AsyncFuture {
fut_one: FutOne,
fut_two: FutTwo,
state: State,
}
// Список состояний нашего `async`-блока
enum State {
AwaitingFutOne,
AwaitingFutTwo,
Done,
}
impl Future for AsyncFuture {
type Output = ();
fn poll(mut 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
будут начинаться там, где завершился предыдущий вызов. Этот процесс продолжается до тех пор, пока футура не будет завершена.
Однако что будет, если async
-блок использует ссылки? Например:
async {
let mut x = [0; 128];
let read_into_buf_fut = read_into_buf(&mut x);
read_into_buf_fut.await;
println!("{:?}", x);
}
Во что скомпилируется эта структура?
struct ReadIntoBuf<'a> {
buf: &'a mut [u8], // указывает на `x` ниже
}
struct AsyncFuture {
x: [u8; 128],
read_into_buf_fut: ReadIntoBuf<'?>, // какое тут время жизни?
}
Здесь футура ReadIntoBuf
содержит ссылку на другое поле нашей структуры, x
. Однако, если AsyncFuture
будет перемещена, положение x
тоже будет изменено, что инвалидирует указатель, сохранённый в read_into_buf_fut.buf
.
Закрепление футур в определённом месте памяти предотвращает эту проблему, делая безопасным создание ссылок на данные за пределами async
-блока.
Как устроено закрепление
Давайте попробуем понять закрепление на более простом примере. Проблема, с которой мы столкнулись выше, — это проблема, которая в конечном итоге сводится к тому, как мы обрабатываем ссылки в самоссылающихся типах в Rust.
Пусть наш пример будет выглядеть так:
#![allow(unused)] fn main() { #[derive(Debug)] struct Test { a: String, b: *const String, } impl Test { fn new(txt: &str) -> Self { Test { a: String::from(txt), b: std::ptr::null(), } } fn init(&mut self) { let self_ref: *const String = &self.a; self.b = self_ref; } fn a(&self) -> &str { &self.a } fn b(&self) -> &String { assert!(!self.b.is_null(), "Test::b called without Test::init being called first"); unsafe { &*(self.b) } } } }
Test
предоставляет методы для получения ссылки на значение полей a
и b
. Поскольку b
является ссылкой на a
, мы храним его как указатель, так как правила заимствования Rust не позволяют нам определять его время жизни. Теперь у нас есть то, что мы называем самоссылающейся структурой.
Наш пример работает нормально, если мы не перемещаем какие-либо наши данные, как вы можете наблюдать в этом примере:
fn main() { let mut test1 = Test::new("test1"); test1.init(); let mut test2 = Test::new("test2"); test2.init(); println!("a: {}, b: {}", test1.a(), test1.b()); println!("a: {}, b: {}", test2.a(), test2.b()); } #[derive(Debug)] struct Test { a: String, b: *const String, } impl Test { fn new(txt: &str) -> Self { Test { a: String::from(txt), b: std::ptr::null(), } } // Мы должны реализовать метод `init`, чтобы сделать ссылку на себя fn init(&mut self) { let self_ref: *const String = &self.a; self.b = self_ref; } fn a(&self) -> &str { &self.a } fn b(&self) -> &String { assert!(!self.b.is_null(), "Test::b called without Test::init being called first"); unsafe { &*(self.b) } } }
Мы получили, что ожидали:
#![allow(unused)] fn main() { a: test1, b: test1 a: test2, b: test2 }
Давайте посмотрим, что произойдёт, если мы поменяем местами test1
с test2
, тем самым переместив данные:
fn main() { let mut test1 = Test::new("test1"); test1.init(); let mut test2 = Test::new("test2"); test2.init(); println!("a: {}, b: {}", test1.a(), test1.b()); std::mem::swap(&mut test1, &mut test2); println!("a: {}, b: {}", test2.a(), test2.b()); } #[derive(Debug)] struct Test { a: String, b: *const String, } impl Test { fn new(txt: &str) -> Self { Test { a: String::from(txt), b: std::ptr::null(), } } fn init(&mut self) { let self_ref: *const String = &self.a; self.b = self_ref; } fn a(&self) -> &str { &self.a } fn b(&self) -> &String { assert!(!self.b.is_null(), "Test::b called without Test::init being called first"); unsafe { &*(self.b) } } }
Очевидно, этот код должен напечатать test1
дважды:
#![allow(unused)] fn main() { a: test1, b: test1 a: test1, b: test1 }
Но вместо этого получаем:
#![allow(unused)] fn main() { a: test1, b: test1 a: test1, b: test2 }
Указатель на test2.b
по-прежнему указывает на старое местоположение, которое сейчас находится внутри test1
. Структура больше не является самоссылающейся, она содержит указатель на поле в другом объекте. Это означает, что мы больше не можем полагаться на то, что время жизни test2.b
будет привязано к времени жизни test2
.
Если вы всё ещё не убеждены, это должно убедить вас:
fn main() { let mut test1 = Test::new("test1"); test1.init(); let mut test2 = Test::new("test2"); test2.init(); println!("a: {}, b: {}", test1.a(), test1.b()); std::mem::swap(&mut test1, &mut test2); test1.a = "I've totally changed now!".to_string(); println!("a: {}, b: {}", test2.a(), test2.b()); } #[derive(Debug)] struct Test { a: String, b: *const String, } impl Test { fn new(txt: &str) -> Self { Test { a: String::from(txt), b: std::ptr::null(), } } fn init(&mut self) { let self_ref: *const String = &self.a; self.b = self_ref; } fn a(&self) -> &str { &self.a } fn b(&self) -> &String { assert!(!self.b.is_null(), "Test::b called without Test::init being called first"); unsafe { &*(self.b) } } }
Диаграмма ниже может помочь визуализировать происходящее:
Из. 1: До и после замены
Здесь легко получить неопределённое поведение и другие ошеломляющие ошибки.
Как использовать закрепление
Давайте посмотрим, как закрепление и тип Pin
могут помочь нам решить эту проблему.
Тип Pin
оборачивает типы указателей, гарантируя, что значения за указателем не будут перемещены, если они не реализуют Unpin
. Например, Pin<&mut T>
, Pin<&T>
, Pin<Box<T>>
гарантируют, что T
не будет перемещён, если T: !Unpin
.
У большинства типов нет проблем с перемещением, так как они реализуют типаж Unpin
. Указатели на типы Unpin
можно свободно помещать в Pin
или извлекать из него. Например, u8
реализует Unpin
, поэтому Pin<&mut u8>
ведёт себя так же, как обычный &mut u8
.
Однако типы, которые нельзя переместить после закрепления, имеют маркер !Unpin
. Футуры, созданные с помощью async/await, являются примером этого.
Закрепление на стеке
Вернёмся к нашему примеру. Мы можем решить нашу проблему, используя Pin
. Давайте посмотрим, как выглядел бы наш пример, если бы вместо этого нам требовался закреплённый указатель:
#![allow(unused)] fn main() { use std::pin::Pin; use std::marker::PhantomPinned; #[derive(Debug)] struct Test { a: String, b: *const String, _marker: PhantomPinned, } impl Test { fn new(txt: &str) -> Self { Test { a: String::from(txt), b: std::ptr::null(), _marker: PhantomPinned, // Делаем наш тип `!Unpin` } } fn init(self: Pin<&mut Self>) { let self_ptr: *const String = &self.a; let this = unsafe { self.get_unchecked_mut() }; this.b = self_ptr; } fn a(self: Pin<&Self>) -> &str { &self.get_ref().a } fn b(self: Pin<&Self>) -> &String { assert!(!self.b.is_null(), "Test::b called without Test::init being called first"); unsafe { &*(self.b) } } } }
Закрепление объекта на стеке всегда будет unsafe
, если наш тип реализует !Unpin
. Вы можете использовать такой крейт, как pin_utils
, чтобы избежать написания собственного unsafe
кода при закреплении на стеке.
Ниже мы закрепляем объекты test1
и test2
на стеке:
pub fn main() { // test1 безопасен для перемещения, пока мы не инициализировали его let mut test1 = Test::new("test1"); // Обратите внимание, как мы затенили `test1` для предотвращения повторного доступа к нему let mut test1 = unsafe { Pin::new_unchecked(&mut test1) }; Test::init(test1.as_mut()); let mut test2 = Test::new("test2"); let mut test2 = unsafe { Pin::new_unchecked(&mut test2) }; Test::init(test2.as_mut()); println!("a: {}, b: {}", Test::a(test1.as_ref()), Test::b(test1.as_ref())); println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.as_ref())); } use std::pin::Pin; use std::marker::PhantomPinned; #[derive(Debug)] struct Test { a: String, b: *const String, _marker: PhantomPinned, } impl Test { fn new(txt: &str) -> Self { Test { a: String::from(txt), b: std::ptr::null(), // Делаем наш тип `!Unpin` _marker: PhantomPinned, } } fn init(self: Pin<&mut Self>) { let self_ptr: *const String = &self.a; let this = unsafe { self.get_unchecked_mut() }; this.b = self_ptr; } fn a(self: Pin<&Self>) -> &str { &self.get_ref().a } fn b(self: Pin<&Self>) -> &String { assert!(!self.b.is_null(), "Test::b called without Test::init being called first"); unsafe { &*(self.b) } } }
Теперь, если мы попытаемся переместить наши данные, мы получим ошибку компиляции:
pub fn main() { let mut test1 = Test::new("test1"); let mut test1 = unsafe { Pin::new_unchecked(&mut test1) }; Test::init(test1.as_mut()); let mut test2 = Test::new("test2"); let mut test2 = unsafe { Pin::new_unchecked(&mut test2) }; Test::init(test2.as_mut()); println!("a: {}, b: {}", Test::a(test1.as_ref()), Test::b(test1.as_ref())); std::mem::swap(test1.get_mut(), test2.get_mut()); println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.as_ref())); } use std::pin::Pin; use std::marker::PhantomPinned; #[derive(Debug)] struct Test { a: String, b: *const String, _marker: PhantomPinned, } impl Test { fn new(txt: &str) -> Self { Test { a: String::from(txt), b: std::ptr::null(), _marker: PhantomPinned, // Делаем наш тип `!Unpin` } } fn init(self: Pin<&mut Self>) { let self_ptr: *const String = &self.a; let this = unsafe { self.get_unchecked_mut() }; this.b = self_ptr; } fn a(self: Pin<&Self>) -> &str { &self.get_ref().a } fn b(self: Pin<&Self>) -> &String { assert!(!self.b.is_null(), "Test::b called without Test::init being called first"); unsafe { &*(self.b) } } }
Система типов не позволяет нам перемещать данные, как показано здесь:
error[E0277]: `PhantomPinned` cannot be unpinned
--> src\test.rs:56:30
|
56 | std::mem::swap(test1.get_mut(), test2.get_mut());
| ^^^^^^^ within `test1::Test`, the trait `Unpin` is not implemented for `PhantomPinned`
|
= note: consider using `Box::pin`
note: required because it appears within the type `test1::Test`
--> src\test.rs:7:8
|
7 | struct Test {
| ^^^^
note: required by a bound in `std::pin::Pin::<&'a mut T>::get_mut`
--> <...>rustlib/src/rust\library\core\src\pin.rs:748:12
|
748 | T: Unpin,
| ^^^^^ required by this bound in `std::pin::Pin::<&'a mut T>::get_mut`
Важно отметить, что закрепление на стеке всегда будет зависеть от гарантий, которые вы даёте при написании
unsafe
. Хотя мы знаем, что указатель типа&'a mut T
закреплён на время жизни'a
, мы не можем знать, перемещаются ли данные, на которые указывает&'a mut T
, после окончания'a
. Если это произойдёт, это нарушит контракт Pin.Ошибка, которую легко сделать, это забыть затенить исходную переменную, так как вы можете удалить
Pin
и переместить данные после&'a mut T
, как показано ниже (что нарушает контракт Pin):fn main() { let mut test1 = Test::new("test1"); let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) }; Test::init(test1_pin.as_mut()); drop(test1_pin); println!(r#"test1.b points to "test1": {:?}..."#, test1.b); let mut test2 = Test::new("test2"); mem::swap(&mut test1, &mut test2); println!("... and now it points nowhere: {:?}", test1.b); } use std::pin::Pin; use std::marker::PhantomPinned; use std::mem; #[derive(Debug)] struct Test { a: String, b: *const String, _marker: PhantomPinned, } impl Test { fn new(txt: &str) -> Self { Test { a: String::from(txt), b: std::ptr::null(), // Делаем наш тип `!Unpin` _marker: PhantomPinned, } } fn init<'a>(self: Pin<&'a mut Self>) { let self_ptr: *const String = &self.a; let this = unsafe { self.get_unchecked_mut() }; this.b = self_ptr; } fn a<'a>(self: Pin<&'a Self>) -> &'a str { &self.get_ref().a } fn b<'a>(self: Pin<&'a Self>) -> &'a String { assert!(!self.b.is_null(), "Test::b called without Test::init being called first"); unsafe { &*(self.b) } } }
Закрепление на куче
Закрепление типа !Unpin
на куче даёт нашим данным постоянный адрес, поэтому мы знаем, что данные, на которые мы указываем, не могут перемещаться после закрепления. В отличие от закрепления на стеке, мы знаем, что данные будут закреплены на время жизни объекта.
use std::pin::Pin; use std::marker::PhantomPinned; #[derive(Debug)] struct Test { a: String, b: *const String, _marker: PhantomPinned, } impl Test { fn new(txt: &str) -> Pin<Box<Self>> { let t = Test { a: String::from(txt), b: std::ptr::null(), _marker: PhantomPinned, }; let mut boxed = Box::pin(t); let self_ptr: *const String = &boxed.a; unsafe { boxed.as_mut().get_unchecked_mut().b = self_ptr }; boxed } fn a(self: Pin<&Self>) -> &str { &self.get_ref().a } fn b(self: Pin<&Self>) -> &String { unsafe { &*(self.b) } } } pub fn main() { let test1 = Test::new("test1"); let test2 = Test::new("test2"); println!("a: {}, b: {}",test1.as_ref().a(), test1.as_ref().b()); println!("a: {}, b: {}",test2.as_ref().a(), test2.as_ref().b()); }
Некоторые функции требуют, чтобы футуры, с которыми они работают, были Unpin
. Чтобы использовать Future
или Stream
, которые не являются Unpin
, с функцией, требующей Unpin
-тип, вам сначала нужно закрепить значение с помощью Box::pin
, создав Pin<Box<T>>
, или макроса pin_utils::pin_mut!
, создав Pin<&mut T>
. Pin<Box<Fut>>
и Pin<&mut Fut>
могут использоваться как футуры, и оба реализуют Unpin
.
Например:
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
Резюме
-
Если
T: Unpin
(что по умолчанию), тоPin<'a, T>
полностью эквивалентен&'a mut T
. Другими словами: "Unpin
" означает, что этот тип можно перемещать, даже если он закреплён, поэтому "Pin
" не повлияет на такой тип. -
Преобразование
&mut T
в закреплённый T требует unsafe, еслиT: !Unpin
. -
Большинство типов стандартной библиотеки реализуют
Unpin
. То же самое касается большинства "обычных" типов, с которыми вы сталкиваетесь в Rust. ТипFuture
, сгенерированный с помощью async/await, является исключением из этого правила. -
Вы можете добавить
!Unpin
к типу в nightly версии с помощью флага опции или добавивstd::marker::PhantomPinned
к вашему типу в стабильной версии. -
Вы можете закрепить данные на стеке или на куче.
-
Для закрепления объекта
!Unpin
на стеке требуетсяunsafe
-
Закрепление объекта
!Unpin
на куче не требуетunsafe
, для этого есть сокращениеBox::pin
. -
Для закреплённых данных, где
T: !Unpin
, вы должны поддерживать инвариант, что их память не будет аннулирована или переназначена с момента закрепления до вызова drop. Это важная часть контракта pin.