Created
January 28, 2020 17:20
-
-
Save cfsamson/9ebe1b8ef0b726969019e8cc4581ae85 to your computer and use it in GitHub Desktop.
Futures, generators and pin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::pin::Pin; | |
pub fn test1() { | |
let mut test1 = Test::new("test1"); | |
test1.init(); | |
let mut test1_pin = Pin::new(&mut test1); | |
let mut test2 = Test::new("test2"); | |
test2.init(); | |
let mut test2_pin = Pin::new(&mut test2); | |
println!( | |
"a: {}, b: {}", | |
Test::a(test1_pin.as_ref()), | |
Test::b(test1_pin.as_ref()) | |
); | |
// try fixing as the compiler suggests. Is there any `swap` happening? | |
// Look closely at the printout. | |
//std::mem::swap(test1_pin.as_mut(), test2_pin.as_mut()); | |
println!( | |
"a: {}, b: {}", | |
Test::a(test2_pin.as_ref()), | |
Test::b(test2_pin.as_ref()) | |
); | |
} | |
#[derive(Debug)] | |
struct Test { | |
a: String, | |
b: *const String, | |
} | |
impl Test { | |
fn new(txt: &str) -> Self { | |
let a = String::from(txt); | |
Test { | |
a, | |
b: std::ptr::null(), | |
} | |
} | |
fn init(&mut self) { | |
let self_ptr: *const String = &self.a; | |
self.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 { | |
unsafe { &*(self.b) } | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::pin::Pin; | |
pub fn test2() { | |
let gen1 = GeneratorA::start(); | |
let gen2 = GeneratorA::start(); | |
// Before we pin the pointers, this is safe to do | |
// std::mem::swap(&mut gen, &mut gen2); | |
// constructing a `Pin::new()` on a type which does not implement `Unpin` is unsafe. | |
// However, as I mentioned in the start of the next chapter about `Pin` a | |
// boxed type automatically implements `Unpin` so to stay in safe Rust we can use | |
// that to avoid unsafe. You can also use crates like `pin_utils` to do this safely, | |
// just remember that they use unsafe under the hood so it's like using an already-reviewed | |
// unsafe implementation. | |
let mut pinned1 = Box::pin(gen1); | |
let mut pinned2 = Box::pin(gen2); | |
// Uncomment these if you think it's safe to pin the values to the stack instead | |
// (it is in this case) | |
//let mut pinned1 = unsafe { Pin::new_unchecked(&mut gen1) }; | |
//let mut pinned2 = unsafe { Pin::new_unchecked(&mut gen2) }; | |
if let GeneratorState::Yielded(n) = pinned1.as_mut().resume() { | |
println!("Got value {}", n); | |
} | |
if let GeneratorState::Yielded(n) = pinned2.as_mut().resume() { | |
println!("Gen2 got value {}", n); | |
}; | |
// This won't work | |
// std::mem::swap(&mut gen, &mut gen2); | |
// This will work but will just swap the pointers. Nothing inherently bad happens here. | |
// std::mem::swap(&mut pinned1, &mut pinned2); | |
let _ = pinned1.as_mut().resume(); | |
let _ = pinned2.as_mut().resume(); | |
} | |
enum GeneratorState<Y, R> { | |
// originally called `CoResult` | |
Yielded(Y), // originally called `Yield(Y)` | |
Complete(R), // originally called `Return(R)` | |
} | |
trait Generator { | |
type Yield; | |
type Return; | |
fn resume(self: Pin<&mut Self>) -> GeneratorState<Self::Yield, Self::Return>; | |
} | |
enum GeneratorA { | |
Enter, | |
Yield1 { | |
to_borrow: String, | |
borrowed: *const String, // Normally you'll see `std::ptr::NonNull` used instead of *ptr | |
}, | |
Exit, | |
} | |
impl GeneratorA { | |
fn start() -> Self { | |
GeneratorA::Enter | |
} | |
} | |
// This tells us that the underlying pointer is not safe to move after pinning. In this case, | |
// only we as implementors "feel" this, however, if someone is relying on our Pinned pointer | |
// this will prevent them from moving it. You need to enable the feature flag | |
// `#![feature(optin_builtin_traits)]` and use the nightly compiler to implement `!Unpin`. | |
impl !Unpin for GeneratorA { } | |
impl Generator for GeneratorA { | |
type Yield = usize; | |
type Return = (); | |
fn resume(self: Pin<&mut Self>) -> GeneratorState<Self::Yield, Self::Return> { | |
// lets us get ownership over current state | |
let this = unsafe { self.get_unchecked_mut() }; | |
match this { | |
GeneratorA::Enter => { | |
let to_borrow = String::from("Hello"); | |
let borrowed = &to_borrow; | |
let res = borrowed.len(); | |
// Tricks to actually get a self reference | |
*this = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()}; | |
match this { | |
GeneratorA::Yield1{to_borrow, borrowed} => *borrowed = to_borrow, | |
_ => () | |
}; | |
GeneratorState::Yielded(res) | |
} | |
GeneratorA::Yield1 {borrowed, ..} => { | |
let borrowed: &String = unsafe {&**borrowed}; | |
println!("{} world", borrowed); | |
*this = GeneratorA::Exit; | |
GeneratorState::Complete(()) | |
} | |
GeneratorA::Exit => panic!("Can't advance an exited generator!"), | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pub fn test3() { | |
let mut gen = GeneratorA::start(); | |
let mut gen2 = GeneratorA::start(); | |
if let GeneratorState::Yielded(n) = gen.resume() { | |
println!("Got value {}", n); | |
} | |
// If you uncomment this, very bad things can happen. This is why we need `Pin` | |
std::mem::swap(&mut gen, &mut gen2); | |
if let GeneratorState::Yielded(n) = gen2.resume() { | |
println!("Got value {}", n); | |
} | |
// this should now start gen2 off | |
if let GeneratorState::Complete(()) = gen.resume() { | |
() | |
}; | |
} | |
// If you've ever wondered why the parameters are called Y and R the naming from | |
// the original rfc most likely holds the answer | |
enum GeneratorState<Y, R> { | |
// originally called `CoResult` | |
Yielded(Y), // originally called `Yield(Y)` | |
Complete(R), // originally called `Return(R)` | |
} | |
trait Generator { | |
type Yield; | |
type Return; | |
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>; | |
} | |
enum GeneratorA { | |
Enter, | |
Yield1 { | |
to_borrow: String, | |
borrowed: *const String, // Normally you'll see `std::ptr::NonNull` used instead of *ptr | |
}, | |
Exit, | |
} | |
impl GeneratorA { | |
fn start() -> Self { | |
GeneratorA::Enter | |
} | |
} | |
impl Generator for GeneratorA { | |
type Yield = usize; | |
type Return = (); | |
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> { | |
// lets us get ownership over current state | |
match self { | |
GeneratorA::Enter => { | |
let to_borrow = String::from("Hello"); | |
let borrowed = &to_borrow; | |
let res = borrowed.len(); | |
// Tricks to actually get a self reference | |
*self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()}; | |
match self { | |
GeneratorA::Yield1{to_borrow, borrowed} => *borrowed = to_borrow, | |
_ => () | |
}; | |
GeneratorState::Yielded(res) | |
} | |
GeneratorA::Yield1 {borrowed, ..} => { | |
let borrowed: &String = unsafe {&**borrowed}; | |
println!("{} world", borrowed); | |
*self = GeneratorA::Exit; | |
GeneratorState::Complete(()) | |
} | |
GeneratorA::Exit => panic!("Can't advance an exited generator!"), | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment