-
-
Save rust-play/b89d17f00f58f071555339093d76a0a3 to your computer and use it in GitHub Desktop.
Code shared from the Rust Playground
This file contains 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
// This is related to https://www.reddit.com/r/rust/comments/goh2be/how_to_store_an_async_future_to_a_function_with_a/ | |
#[derive(Debug)] | |
struct MyStruct { | |
a: usize, | |
b: usize, | |
c: String, | |
// ... | |
} | |
struct StructWithCallback<T> { | |
callback: T, | |
} | |
impl<T> StructWithCallback<T> { | |
fn new(cb: T) -> Self { | |
Self { | |
callback: cb | |
} | |
} | |
} | |
async fn my_function_0<'r>(my_struct: &'r mut MyStruct) { | |
println!("in my_function_0: {:?}", my_struct); | |
// Change stuff. | |
my_struct.a += 1; | |
my_struct.b += 2; | |
my_struct.c = "a different string".to_string(); | |
} | |
async fn my_function_1<'r>(my_struct: &'r mut MyStruct) { | |
println!("in my_function_1: {:?}", my_struct); | |
// Change stuff. | |
my_struct.a += 1; | |
my_struct.b += 2; | |
my_struct.c = "a completely different string".to_string(); | |
} | |
async fn example() { | |
let mut callbacks = vec![ | |
StructWithCallback::new(my_function_0), | |
StructWithCallback::new(my_function_1) | |
]; | |
let mut my_struct = MyStruct { | |
a: 1, | |
b: 2, | |
c: "a string".to_string(), | |
}; | |
// Invoke the stored callbacks. | |
let callback_0 = callbacks[0].callback; | |
callback_0(&mut my_struct).await; | |
let callback_1 = callbacks[0].callback; | |
callback_1(&mut my_struct).await; | |
// Confirm stuff changed. | |
println!("after my_function: {:?}", my_struct); | |
} | |
fn main() { | |
let mut rt = tokio::runtime::Runtime::new().unwrap(); | |
rt.block_on(example()); | |
} |
I answered the wrong question there...
The reason that Box<dyn Trait>
works for goose is that it has space in the syntax for the for<'r> lifetime to be applied to the Future (using +). When I tried to change it into a type parameter, I had nowhere to put the lifetime, and so the borrow checker got very sad.
@LionsAd: https://gist.github.com/87ab93979d770314e6698a9867d1e7e5 does what we want. I will have a go at patching Goose to use this pattern.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The trick we are using in goose returns a
Pin<Box<dyn Future<Output=()>>>
, which is a concrete type (Box<dyn ...>) with a concrete in-memory layout (fat pointer to (Future object, vtable for all the methods in the Future trait)) so it can safely be stored in a vec.The dyn keyword is the crucial bit here: it implies dynamic dispatch, using a vtable.
Boxing also gets around the fact that the different Future implementations may be differently sized. I generally expect to see Box and dyn together. Box coerces the differently-sized objects into a single size (a pointer to the object on the heap) and the other coerces the different implementations of the Trait into a single thing (pointer to a vtable).