To handle I/O, my emulator uses a table of function pointers. The problem is that many of these functions are within structs and require &self or &mut self pointers.
In C, this was easily solvable -- I could store a void* that held a reference to self:
In JavaScript, I could call .bind on the function, implicitly passing the this pointer whenever I called it.
Ideally, I'd like to prevent creating function pointer types for each device -- I'd like single unified function pointer for all reads, another for writes, so on. I was looking into using raw pointers, but it would be nice to avoid unsafe code. Is there a way to do this elegantly and cleanly in Rust?
The reason that I can't use a simple match port {} (which would be infinitely easier) is that some devices can have their I/O addresses remapped at runtime.
If you know the type and method to call, then take advantage of the fact that:
object.method();
is the same as:
ObjectType::method(object);
So self is not special. It's just the first argument to a function.
You should also be able to coerce method pointer to fn(&ObjectType) function pointer.
let fnpointer: fn(&ObjectType) = ObjectType::some_method;
If you don't know types, then use closures. Closures are fat pointers (data pointer + function pointer), so you will need double indirection Box<Box<dyn FnMut()>> to be able to cast them as a thin void*.
How would I store these &mut self references? In C, I just stored the void* opaque stuff in a separate array, and it worked because C was rather flexible with its casts. I was thinking of using an array of *mut _ and casting to my desired pointer type using .as_ref(), but I'm wondering if there's a better solution.
&mut is not a general-purpose pointer like in C, but a temporary borrow tied to a scope. It's super dangerous to use it with C callbacks, unless you can guarantee these callbacks will happen synchronously in the same scope, and not later.
You would typically use Box::new(object) to guarantee a stable address for it (otherwise objects are on stack and can move). And then Box::into_raw to pass ownership to C, or cast to a pointer with as if you're lending to C.
Oh, and you don't store the object as &mut . It's not possible - temporary borrows can't store any data by definition. You only temporarily borrow some other type as &mut at the last second when you call a method.
How about storing addr->device mapping in a HashMap<u16, Device>? You can define a Device struct, e.g.:
struct Device {
// some state
}
impl Device {
fn read(&self) {}
fn write(&mut self, data: u32) {}
}
Then populate the map with devices and their initial port addresses. If the port is remapped, update the map. You can similarly use an array instead of a map and maintain the devices in there in a similar manner. Pseudocode:
let mut devices: HashMap<u16, Device> = ...
loop {
// simulation
let port: u16 = ...;
devices.get(&port).unwrap().read();
...
let data: u32 = ...;
devices.get_mut(&port).unwrap().write(data);
...
// check if ports remapped, if so, update `devices`
}
I really like the idea, but how would you go around creating multiple kinds of devices? I see how this could work with abstract classes, like the ones in Java, and perhaps it could work with traits. But I don't believe there's a way to do HashMap<u16, DeviceTrait>, unfortunately.
As @mbrubeck mentioned, you can use trait objects to abstract away the device impl. Another option is to use an enum if the set/type of devices is known a priori and controlled by your simulation.