[Closed] Need help with returning one of a few functions from another function


#1

I am just learning rust, coming from Python. I am trying to write a Modbus Server as my first project. In Modbus, a client can request data of certain types through the use of a function code field. I am trying to have a function that reads the function code field from the request and returns the function which can handle that function code. That function is called get_modbus_function in the lib.rs file linked below. I have functions implemented for two of the possible function codes: read_coils and read_holding registers.

The problem I am having is understanding how to make this generic in order to handle the different types of read_coils and read_holding_registers.

Here is the compiler errors:

Compiling playground1 v0.1.0 (file:///home/keith/code/rust-playground)
src/lib.rs:84:32: 84:42 error: mismatched types:
 expected `fn(ReadRequest, &collections::vec::Vec<T>) -> core::result::Result<U, ModbusError>`,
    found `fn(ReadRequest, &collections::vec::Vec<bool>) -> core::result::Result<ModbusResponse<bool>, ModbusError> {read_coils}`
(expected type parameter,
    found bool) [E0308]
src/lib.rs:84         READ_COILS => Box::new(read_coils),
                                             ^~~~~~~~~~
src/lib.rs:84:32: 84:42 help: run `rustc --explain E0308` to see a detailed explanation
src/lib.rs:85:44: 85:66 error: mismatched types:
 expected `fn(ReadRequest, &collections::vec::Vec<T>) -> core::result::Result<U, ModbusError>`,
    found `fn(ReadRequest, &collections::vec::Vec<u16>) -> core::result::Result<ModbusResponse<u16>, ModbusError> {read_holding_registers}`
(expected type parameter,
    found u16) [E0308]
src/lib.rs:85         READ_HOLDING_REGISTERS => Box::new(read_holding_registers),
                                                         ^~~~~~~~~~~~~~~~~~~~~~
src/lib.rs:85:44: 85:66 help: run `rustc --explain E0308` to see a detailed explanation
    src/lib.rs:86:23: 86:33 error: mismatched types:
     expected `fn(ReadRequest, &collections::vec::Vec<T>) -> core::result::Result<U, ModbusError>`,
        found `fn(ReadRequest, &collections::vec::Vec<bool>) -> core::result::Result<ModbusResponse<bool>, ModbusError> {read_coils}`
    (expected type parameter,
        found bool) [E0308]
src/lib.rs:86         _ => Box::new(read_coils)
                                    ^~~~~~~~~~
src/lib.rs:86:23: 86:33 help: run `rustc --explain E0308` to see a detailed explanation
error: aborting due to 3 previous errors
Could not compile `playground1`.

I am not sure if this is possible or not. I have read the book and tried searching online. I am not exactly sure what to search for. Any pointers would be greatly appreciated.

Repository


#2

The core issue is that you can’t have definitions of types depend on data which is only available at runtime. This is a concept aptly named dependent typing, and Rust currently doesn’t have it.

You’re trying to return one of two different function types from get_modbuf_function():

  • fn(ReadRequest, &Vec<bool>) -> ModbusResult<ReadCoilResponse>
  • fn(ReadRequest, &Vec<u16>) -> ModbusResult<ReadHoldingRegisterResponse>

Syntatically, it might make sense to you to collapse these as type parameters. But the problem here is that type parameters on a function are so-called input types; they’re chosen by the caller, either explicitly or via inference at the call site. You don’t have control over them. A user of this API might try to call get_modbus_function::<String, TcpStream>(255) but the compiler has no idea how to coerce the function pointers returned to fn(ReadRequest, &Vec<String>) -> ModbusResult<TcpStream>.

All you can really do is match on function_code at a higher level and call read_coil() or read_holding_registers() at the same time. If it’s any consolation, in the new form the compiler will be more effective at optimizing the relevant code, as function pointers represent a boundary that is rather difficult to optimize across.

As a side note, you don’t need to put fn(...) -> ... types in a Box because they’re already pointers themselves. You only need to Box closures (made with the |<argument,*>| <expression | block> syntax), to erase their compiler-generated types and collapse them to trait objects, i.e. Box<Fn(...) -> ...>, Box<FnMut(...) -> ...> and Box<FnOnce(...) -> ...> (which needs some hackery to actually work). Of course, a boxed function pointer can also be coerced to a closure trait object so that they can both fit in the same space, e.g. Box<fn(A, B) -> C> will coerce to Box<Fn(A, B) -> C> so they can fit in the same variable binding/struct field/etc.


#3

Thanks for the response, I was wondering if I was trying to do something that wasn’t allowed.