Compare function pointers for equality

I am trying to compare two function objects to see if they point to the same function. However I can't seem to figure out how to do that. I get this error with the below sample code

error[E0369]: binary operation `==` cannot be applied to type `for<'r> fn(&'r u64) -> u64`
fn foo(x: &u64) -> u64 { x + 5 }

fn bar(func1: fn(&u64) -> u64, func2: fn(&u64) -> u64) -> u64 {
    assert!(func1 == func2);
    func1(&7) + func2(&3)
}

fn main() {
    println!("{:p}", &foo);
    bar(foo, foo);
}

if I change the function signature from (&u64) -> u64 to (u64) -> u64 it all works. I am not sure why there is an issue if the function signature contains references.

One way to solve this is

fn foo(x: &u64) -> u64 { x + 5 }

fn bar(func1: fn(&u64) -> u64, func2: fn(&u64) -> u64) -> u64 {
    assert!(func1 as fn(&'static _) -> _ == func2);
    func1(&7) + func2(&3)
}

fn main() {
    println!("{:p}", &foo);
    bar(foo, foo);
}
2 Likes

If they're the same function, their addresses will be identical, and since functions with different lifetimes are still compiled to the same machine code, you don't need to worry about the HRTB errors you're getting:

fn bar(func1: fn(&u64) -> u64, func2: fn(&u64) -> u64) -> u64 {
    assert!(func1 as usize == func2 as usize);
    func1(&7) + func2(&3)
}
2 Likes

It's not guaranteed that function pointers to the same function have same address. Same generic function with same type parameters can be monomorphised in different compilation unit independently.

5 Likes

LLVM may also merge functions from completely different origins if they end up generating the same code.

7 Likes

since functions with different lifetimes are still compiled to the same machine code

Is that the reason it wouldn't let me compare them? Because the functions could have different lifetimes (which makes they type "different" according to rust)?

In a sense you're right, however the view I came up with as to why it doesn't work is this: HRTBs-ed types (like your function) are groups of types, which have an infinite number of members (because there could exist an infinite number of lifetimes in a rust program). Hence we are restricted to comparing discrete functions (by forcibly resolving them as @steffahn has done) or completely ignoring them as I exemplified.

Note however that this does suffer from the issues as presented by @cuviper and @Hyeonu, and such something like so might be result in true:

fn compare(a: fn(usize) -> u32, b: fn(String) -> u32) {
    assert_eq!(a as usize, b as usize);
}

fn foo<T>(_: T) -> u32 {
    0
}

fn main() {
    compare(foo::<usize>, foo::<String>);
}

On a side note, I just found out that this:

fn compare(a: fn(usize) -> u32, b: fn(u64) -> u32) {
    assert_eq!(a as usize, b as usize);
}

fn foo<T>(_: T) -> u32 {
    0
}

fn main() {
    compare(foo::<usize>, foo::<u64>);
}

Panics on the playground (which is a 64 bit env) with:

   Compiling playground v0.0.1 (/playground)
    Finished release [optimized] target(s) in 0.67s
     Running `target/release/playground`
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `94147381531296`,
 right: `94147381531296`', src/main.rs:2:5

Playground.

Either I'm missing something crucial or this is a bug?

There are a few bugs with function address equality noted here:
https://github.com/rust-lang/unsafe-code-guidelines/issues/239

2 Likes

It is a bug that the equality operator can't be used in this case. It's a language/library restriction that the required PartialEq impl doesn't exist.

https://github.com/rust-lang/rust/issues/45048 and https://github.com/rust-lang/rust/issues/54508#issuecomment-424838318 (These bugs and others - it's hard to overview. It's been a known problem from issues I saw before then too).

It looks like a new language feature is needed to resolve this correctly. A language feature that makes it possible to blanket implement traits across all function pointer types (and similar, fn items?), not just a "hand picked" subset of them.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.