Best practices for &self in array of function pointers

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:

void write_port(void* opaque, uint32_t port, uint32_t data) {
    device_t* self = opaque;
    self->field = some_value; 
}

write_ports[port & 0xFFFF](devs[port & 0xFFFF], port, data);

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*.

2 Likes

Thanks for the pointers (no pun intended haha).

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`
}

Maybe there's something crucial I'm missing though?

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.

Trait objects are one way to store pointers to multiple types in a single collection.

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.

Yes, the latter is what I've decided to go with.

Thank you all for your help, really appreciate it.