Last active
May 3, 2024 20:24
-
-
Save aisamanra/da7cdde67fc3dfee00d3 to your computer and use it in GitHub Desktop.
Creating a HashMap of closures in Rust
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
#![feature(unboxed_closures)] | |
#![feature(core)] | |
#![feature(io)] | |
use std::old_io::stdio::{stdin}; | |
use std::collections::HashMap; | |
// This is our toy state example. | |
#[derive(Debug)] | |
struct State { | |
x: i64, | |
y: i64, | |
} | |
// This is a type alias for a _boxed function_. This requires a bit of | |
// picking apart, so bit-by-bit: | |
// - the reason we need a Box is because the size of a function is | |
// not always guaranteed to be the same from function to function. | |
// In order to put a thing in a HashMap, it /must/ have a known | |
// size! So we put it behind a layer of indirection by adding | |
// Box<...> around it. Boxes are always the same size. | |
// - The thing inside the Box has two bounds on it: one is that | |
// it is 'static, which means it lives for the entire length of | |
// the program. This is simple enough. | |
// - The other is the trait Fn<(&'a mut State,),Output=()>, which | |
// itself has two parts: the first argument, which tells you what | |
// its argument type is, and the second, which tells you what its | |
// output type is. this means it's a function that takes a mutable | |
// reference to a State as argument, and produces nothing as output. | |
type Callback<'a> = Box<(Fn<(&'a mut State,),Output=()> + 'static)>; | |
// This is just so we don't get newlines on the end of our input. | |
// Not all that elaborate here. | |
fn read_line() -> String { | |
let mut s = stdin().read_line().unwrap(); | |
let l = s.len(); | |
s.truncate(l - 1); | |
return s; | |
} | |
// This takes a function of a callback type and wraps it in a | |
// box, which allows us to gloss over the differences between different | |
// callbacks and put them in the same HashMap. If we didn't do this, | |
// Rust would complain that we're trying to put different functions into | |
// the same HashMap. By wrapping them like this, Rust allows us to | |
// treat them as though they are "the same thing" and keep them in the | |
// same structure. | |
fn mk_callback<'a, F>(f: F) -> Callback<'a> | |
where F: Fn<(&'a mut State,),Output=()> + 'static { | |
Box::new(f) as Callback | |
} | |
fn main() { | |
// this state is mutable, and can be changed below... | |
let mut state = State { x: 0, y: 0 }; | |
// but this hashmap can't be changed after it's created! | |
let callbacks = { | |
// we do this trick by creating it in a local scope as mutable | |
let mut h = HashMap::new(); | |
// adding the relevant callbacks... | |
h.insert("w", mk_callback(|st: &mut State| st.y -= 1 )); | |
h.insert("a", mk_callback(|st: &mut State| st.x -= 1 )); | |
h.insert("s", mk_callback(|st: &mut State| st.y += 1 )); | |
h.insert("d", mk_callback(|st: &mut State| st.x += 1 )); | |
// and then returning it to the outer scope, which means that | |
// it is no longer mutable. | |
h }; | |
// We loop forever (or until we break) | |
loop { | |
println!("Current state: {:?}", state); | |
// this will read in a line without a newline afterwards | |
let key = read_line(); | |
// break if it's Q, otherwise... | |
if key == "q" { | |
break; | |
} else { | |
// look it up in our callbacks hash, and if it's | |
// present, call it! | |
callbacks.get(key.as_slice()).map({ |fun| fun(&mut state) }); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Can you show this for async? I am not sure if https://docs.rs/futures/latest/futures/future/type.BoxFuture.html helps