Is there any plan to implement basic traits on function pointers that don't take by value?

Everything is in the title. It's a bit strange that this works:

#[derive(Debug)]
struct Foo {
    foo: fn(u32),
}

but this doesn't:

#[derive(Debug)]
struct Foo {
    foo: fn(&u32),
}

From what I can tell it's also the case for all the other derivables.

1 Like

One problem is that library code does not have a good way to say "impl trait Foo for every fn type" so this would need special-case support in the compiler, or a new language feature to fix this limitation. Issue #24000 and #26082 have some more discussion.

1 Like

Yeah I've read those. I figured so much from checking out libcore/ptr.rs. I understand it'd be unreasonable to write implementations for all combinations of T, &T and &mut T arguments + arity like that.
I just couldn't find any RFC or open issue about that nor any plan to address it so I thought I'd bring it up, in case I missed something or that info is somewhat internal.

I still don't understand what is going on but it doesn't seem like a case of needing "impl trait Foo for every fn type" as @mbrubeck commented.

Let's consider just functions with a single argument:

trait Debug {}

// libcore/ptr.rs contains impls like this
impl<Ret, A> Debug for extern "Rust" fn(A) -> Ret {}

fn by_val(_: u32) {}
fn by_ref(_: &u32) {}

fn take_fn<Ret, A>(_: fn(A) -> Ret) {}

fn take_debug<T: Debug>(_: T) {}

fn main() {
    take_fn(by_val); // ok
    take_fn(by_ref); // ok
    
    take_debug(by_val); // the trait `Debug` is not implemented for `fn(u32) {by_val}`
    take_debug(by_ref); // the trait `Debug` is not implemented for `fn(&u32) {by_ref}`
    
    take_fn(by_val as fn(u32)); // ok
    take_fn(by_ref as fn(&u32)); // ok
    
    take_debug(by_val as fn(u32)); // ok
    take_debug(by_ref as fn(&u32)); // the trait `Debug` is not implemented for `fn(&u32)`
}

The functions take_fn and take_debug seem like they should behave very similarly in terms of type inference, but for some reason take_fn is way more usable than take_debug. I am especially confused by the last error - why is it that fn(&u32) matches fn(A) -> Ret in the case of take_fn but not in the case of impl Debug?

EDIT: Now I'm even more confused.

    take_debug(by_ref as fn(_)); // ok

Your expectation is that when the compiler sees take_debug(by_val), it coerces by_val into an appropriate type. The problem is that it doesn't know what type it's supposed to coerce to. All it sees is this T thing, which it hasn't assigned to a type yet.

So it assigns the only type is has on hand: fn(u32) {by_val}... which doesn't implement Debug.

For this to work, the compiler would have to assign the type, fail to find the trait implementation, and then coerce the original input into a different type and re-try... possibly more than once. I dunno how I feel about that level of magic being involved.

take_fn is fine because there's exactly coercion it can perform, so the behaviour is nice and simple.

As for ... as fn(_), all you're doing there is explicitly triggering the conversion. Once it knows it has to coerce, it can fill in the argument types itself (which is what happens when it coerces for take_fn, and then infers the type of A).

Thanks, that makes sense for take_debug(by_val) and take_debug(by_ref) but what is the deal with this one? Isn't this an explicitly triggered conversion to a function pointer that has a debug impl?

take_debug(by_ref as fn(&u32)); // the trait `Debug` is not implemented for `fn(&u32)`

And what accounts for the difference between that vs this?

take_debug(by_ref as fn(_)); // ok

I think this is the core of @Letheed's original issue, which is that #[derive(Debug)] with foo: fn(&u32) probably fails for exactly the same reason that take_debug(by_ref as fn(&u32)) fails. Unfortunately in that case there is no way to work around it like with take_debug(by_ref as fn(_)).

Yeah, I have no idea what the deal with that specific one is; I was just commenting on the generics+coercion thing.

Whatever the issue is, it looks like there is a way for #[derive(Debug)] to work around it. The generated code looks like this:

impl ::std::fmt::Debug for Foo {
    fn fmt(&self, __arg_0: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
        match *self {
            Foo { foo: ref __self_0_0 } => {
                let mut builder = __arg_0.debug_struct("Foo");
                let _ = builder.field("foo", &&(*__self_0_0)); // the trait `std::fmt::Debug` is not implemented for `fn(&u32)`
                builder.finish()
            }
        }
    }
}

If it can notice the fn(...) and generate this instead, everything works:

impl ::std::fmt::Debug for Foo {
    fn fmt(&self, __arg_0: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
        match *self {
            Foo { foo: ref __self_0_0 } => {
                let mut builder = __arg_0.debug_struct("Foo");
                let _ = builder.field("foo", &&(*__self_0_0 as fn(_))); // ok
                builder.finish()
            }
        }
    }
}

It wouldn't work if you are using fn(&u32) behind a typedef like type MyFn = fn(&u32) but at least it would solve our issue?

This makes things a bit more complicated:

#![feature(core_intrinsics)]

trait Debug {
    fn which() -> &'static str;
}

// I would have guessed that these impls conflict.
impl<Ret, A> Debug for extern "Rust" fn(A) -> Ret {
    fn which() -> &'static str { "fn(A) -> Ret" }
}
impl<Ret, A> Debug for extern "Rust" fn(&A) -> Ret {
    fn which() -> &'static str { "fn(&A) -> Ret" }
}

fn by_val(_: u32) {}
fn by_ref(_: &u32) {}

fn main() {
    print_which(by_val as fn(_)); // fn(u32): fn(A) -> Ret
    print_which(by_ref as fn(_)); // fn(&u32): fn(A) -> Ret
    print_which(by_ref as fn(&_)); // fn(&u32): fn(&A) -> Ret
}

fn print_which<T: Debug>(_: T) {
    println!("{}: {}", unsafe { std::intrinsics::type_name::<T>() }, T::which());
}

The reason this is broken is that fn(&u32) is for<'a> fn(&'a u32). The stdlib contains impls on all fn(A,B,C, ..)-> R types up to a certain number of arguments, but not on for<'a> fn(A, B, C, ...) -> R types. Nor is it easy to add such blanket impls since you need to get all combinations of lifetimes on arguments, and we don't have higher kinded types anyway, so you can't even specify A<'a> if A is a type parameter.

the trait Debug is not implemented for fn(u32) {by_val}

These are "specific function types"; all functions have their own fn(signature) {fn_name} type which can be coerced to a fn pointer. I don't recall why this exists, but it does, and there's similarly no way to have a blanket impl on these.

There needs to be a way to specify blanket impls for such types in the stdlib, but there isn't right now. Variadic generics may help but will not solve this situation.

4 Likes