Skip to content

Instantly share code, notes, and snippets.

@aisamanra
Last active May 3, 2024 20:24
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save aisamanra/da7cdde67fc3dfee00d3 to your computer and use it in GitHub Desktop.
Save aisamanra/da7cdde67fc3dfee00d3 to your computer and use it in GitHub Desktop.
Creating a HashMap of closures in Rust
#![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) });
}
}
}
@mik30s
Copy link

mik30s commented Mar 15, 2018

Thanks!

@celavek
Copy link

celavek commented Jul 1, 2022

Awesome!

@brandonros
Copy link

Can you show this for async? I am not sure if https://docs.rs/futures/latest/futures/future/type.BoxFuture.html helps

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment