Skip to content

Instantly share code, notes, and snippets.

@cfsamson
Created January 28, 2020 17:20
Show Gist options
  • Save cfsamson/9ebe1b8ef0b726969019e8cc4581ae85 to your computer and use it in GitHub Desktop.
Save cfsamson/9ebe1b8ef0b726969019e8cc4581ae85 to your computer and use it in GitHub Desktop.
Futures, generators and pin
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) }
}
}
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!"),
}
}
}
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