Type missmatch, but they are actually the same type... (bug?)

Hi folks,

I actually have a quite "interesting" problem.

Start with this piece of code:

#[cfg(feature = "pro")]
let write_aof = Some(WriteAOF);
#[cfg(not(feature = "pro"))]
let write_aof = None;

let mut types = r::ffi::RedisModuleTypeMethods {
    version: 1,
    rdb_load: Some(rdb_load),
    rdb_save: Some(rdb_save),
    aof_rewrite: write_aof,
    mem_usage: None,
    digest: None,
    free: Some(free_db),
};

where the idea is to include or not include a particular function write_aof if a features is on or off.

This code does not compile and provide this error.

error[E0308]: mismatched types
   --> src/lib.rs:698:22
    |
698 |         aof_rewrite: write_aof,
    |                      ^^^^^^^^^ expected fn pointer, found fn item
    |
    = note: expected type `std::option::Option<unsafe extern "C" fn(*mut redisql_lib::redis::ffi::RedisModuleIO, *mut redisql_lib::redis::ffi::RedisModuleString, *mut std::os::raw::c_void)>`
               found type `std::option::Option<unsafe extern "C" fn(*mut redisql_lib::redis::ffi::RedisModuleIO, *mut redisql_lib::redis::ffi::RedisModuleString, *mut std::os::raw::c_void) {engine_pro::WriteAOF}>`
    = help: here are some functions which might fulfill your needs:

Which already is not very clear and I am not quite sure what I am doing wrong.

But what I believe is even more interesting is the fact that this other code does compile perfectly.

    #[cfg(feature = "pro")]
    let mut types = r::ffi::RedisModuleTypeMethods {
        version: 1,
        rdb_load: Some(rdb_load),
        rdb_save: Some(rdb_save),
        aof_rewrite: WriteAOF,
        mem_usage: None,
        digest: None,
        free: Some(free_db),
    };

    #[cfg(not(feature = "pro"))]
    let mut types = r::ffi::RedisModuleTypeMethods {
        version: 1,
        rdb_load: Some(rdb_load),
        rdb_save: Some(rdb_save),
        aof_rewrite: None,
        mem_usage: None,
        digest: None,
        free: Some(free_db),
    };

It is juts me doing something very stupid and not seeingg it, or I should fill some bug report?

Here is a minimized version of what you are seeing.

struct S {
    f: Option<fn()>,
}

fn write() {}

fn main() {
    let write_aof = Some(write);
    let _ = S {
        f: write_aof,
    };
}
error[E0308]: mismatched types
  --> src/main.rs:10:12
   |
10 |         f: write_aof,
   |            ^^^^^^^^^ expected fn pointer, found fn item
   |
   = note: expected type `std::option::Option<fn()>`
              found type `std::option::Option<fn() {write}>`

They are legitimately different types! The type fn() {write} has zero size. The single legal value of type fn() {write} is write. Meanwhile fn() is the size of a pointer. There are many legal values of type fn() so it is represented as a function pointer at runtime.

fn write() {}

fn main() {
    println!("{}", std::mem::size_of_val(&write)); // 0
    println!("{}", std::mem::size_of_val(&(write as fn()))); // 8
}

The advantage of this representation is that functions passed as a generic argument can be optimized better and do not necessarily involve invoking through a function pointer.

fn three() -> i32 { 3 }
fn four() -> i32 { 4 }

// We codegen add_one<fn() -> i32 {three}> and add_one<fn() -> i32 {four}>
// separately so they can be optimized independently.
fn add_one<F>(f: F) -> i32
where
    F: FnOnce() -> i32
{
    f() + 1
}

fn main() {
    println!("{}", add_one(three));
    println!("{}", add_one(four));
}

You can work around this by allowing a conversion from fn() {write} to fn(), in your case Some(WriteAOF as _) instead of Some(WriteAOF).

4 Likes