Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Traits on function pointers #100116

Open
jdonszelmann opened this issue Aug 3, 2022 · 2 comments
Open

Traits on function pointers #100116

jdonszelmann opened this issue Aug 3, 2022 · 2 comments
Labels
A-diagnostics Area: Messages for errors, warnings, and lints C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@jdonszelmann
Copy link
Contributor

jdonszelmann commented Aug 3, 2022

First of all, I'm not yet sure if this is a problem with just diagnostics, or with the entire type system.
The following is a minimal example of an issue I got working on a larger project. The example may seem like a weird thing to do
but that's because it's minimized.

enum MyEnum {
    A,
    B(Box<Self>)
}
trait MyTrait1 {}
impl MyTrait1 for MyEnum {}

trait MyTrait2<T: MyTrait1> {}
impl<T: MyTrait1> MyTrait2<T> for fn(Box<T>) -> T {}

fn test<A: MyTrait1, B: MyTrait2<A>>(v: B) {}

fn main() {
    test(MyEnum::B);
}

(https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=5f3e7f2b710cec461b0f4f824583fa69)
I expected this to work, but it doesn't. It gives the following (weird) error:

Compiling playground v0.0.1 (/playground)
error[[E0277]](https://doc.rust-lang.org/stable/error-index.html#E0277): the trait bound `fn(Box<MyEnum>) -> MyEnum {MyEnum::B}: MyTrait2<_>` is not satisfied
  --> src/main.rs:14:10
   |
14 |     test(MyEnum::B);
   |     ---- ^^^^^^^^^ the trait `MyTrait2<_>` is not implemented for `fn(Box<MyEnum>) -> MyEnum {MyEnum::B}`
   |     |
   |     required by a bound introduced by this call
   |
   = help: the trait `MyTrait2<T>` is implemented for `fn(Box<T>) -> T`
note: required by a bound in `test`
  --> src/main.rs:11:25
   |
11 | fn test<A: MyTrait1, B: MyTrait2<A>>(v: B) {}
   |                         ^^^^^^^^^^^ required by this bound in `test`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` due to previous error

What's weird about it is this: it doesn't seem to think MmyTrait2 is implemented for fn(Box<MyEnum>) -> MyEnum
however, the help messages says: look, it is implemented for fn(Box<T>) -> T which my type conforms to.

I started digging a bit. Maybe it's because of the enum variant not actually being a fn:

enum MyEnum {
    A,
    B(Box<Self>)
}
trait MyTrait1 {}
impl MyTrait1 for MyEnum {}

trait MyTrait2<T: MyTrait1> {}
impl<T: MyTrait1> MyTrait2<T> for fn(Box<T>) -> T {}

fn test<A: MyTrait1, B: MyTrait2<A>>(v: B) {}

fn helper(a: Box<MyEnum>) -> MyEnum {
    MyEnum::B(a)
}

fn main() {
    test(helper);
}

But this gives a similar error.

Alright, maybe it's the function pointer. What happens if I ask for a closure? (I'd quite like the function pointers since with closures I need a generic leading to overlapping impls, and I never expect to get a closure anyway. I thought that was the problem at first, distinguishing an overlapping impl with the fn pointer but the example seems to prove that it goes wrong even if there aren't any other implementors of MyTrait2)

enum MyEnum {
    A,
    B(Box<Self>)
}
trait MyTrait1 {}
impl MyTrait1 for MyEnum {}

trait MyTrait2<T: MyTrait1> {}
impl<T: MyTrait1, F: Fn(Box<T>) -> T> MyTrait2<T> for F {}

fn test<A: MyTrait1, B: MyTrait2<A>>(v: B) {}

fn helper(a: Box<MyEnum>) -> MyEnum {
    MyEnum::B(a)
}

fn main() {
    test(helper);
}

This seems to work. So it specifically has to do with function pointers.

Now one more thing, if I insert an explicit cast to the function pointer type:

enum MyEnum {
    A,
    B(Box<Self>)
}
trait MyTrait1 {}
impl MyTrait1 for MyEnum {}

trait MyTrait2<T: MyTrait1> {}
impl<T: MyTrait1> MyTrait2<T> for fn(Box<T>) -> T {}

fn test<A: MyTrait1, B: MyTrait2<A>>(v: B) {}

fn main() {
    test::<_, fn(Box<MyEnum>) -> MyEnum>(MyEnum::B);
}

Everything works.

(last minute: I thought maybe the box was the issue so I removed it and the enum, replacing it with just a function call and even that produces the same error)
trait MyTrait1 {}
impl MyTrait1 for i64 {}

trait MyTrait2<T: MyTrait1> {}
impl<T: MyTrait1> MyTrait2<T> for fn(T) -> T {}

fn test<A: MyTrait1, B: MyTrait2<A>>(v: B) {}

fn helper(a: i64) -> i64 {
    todo!()
}

fn main() {
    test(helper);
}

breaks in a similar manner:

Compiling playground v0.0.1 (/playground)
error[[E0277]](https://doc.rust-lang.org/stable/error-index.html#E0277): the trait bound `fn(i64) -> i64 {helper}: MyTrait2<_>` is not satisfied
  --> src/main.rs:14:10
   |
14 |     test(helper);
   |     ---- ^^^^^^ the trait `MyTrait2<_>` is not implemented for `fn(i64) -> i64 {helper}`
   |     |
   |     required by a bound introduced by this call
   |
   = help: the trait `MyTrait2<T>` is implemented for `fn(T) -> T`
note: required by a bound in `test`
  --> src/main.rs:7:25
   |
7  | fn test<A: MyTrait1, B: MyTrait2<A>>(v: B) {}
   |                         ^^^^^^^^^^^ required by this bound in `test`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` due to previous error
My conclusion: this is certainly a diagnostics bug. The help message that the implementation exists is misleading since it references an implementation that looks like it should work. However, I also expected this to *just work* meaning it could be classified as a bug in type inference. But I don't know enough about the compiler to know whether or not this is expected behavior or not.

Meta

rustc --version --verbose:

rustc 1.61.0 (fe5b13d68 2022-05-18)
binary: rustc
commit-hash: fe5b13d681f25ee6474be29d748c65adcd91f69e
commit-date: 2022-05-18
host: x86_64-unknown-linux-gnu
release: 1.61.0
LLVM version: 14.0.0

(though I also tried nightly, and it's not fixed yet there)

(there is no backtrace here since there's no program that crashed. Code just didn't compile)

I'm mentioning @estebank since I can't add the A-diagnostics tag myself on a more generic bug report. That only happens if I choose the diagnostics template and I think this bug may be broader than that. I hope you don't mind :)

@jdonszelmann jdonszelmann added the C-bug Category: This is a bug. label Aug 3, 2022
@estebank estebank added A-diagnostics Area: Messages for errors, warnings, and lints T-lang Relevant to the language team, which will review and decide on the PR/issue. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Aug 3, 2022
@compiler-errors
Copy link
Member

This is two things.

  1. This is a legitimate code bug, since MyEnum::B is not a function pointer, but a zero-sized function item: https://doc.rust-lang.org/reference/types/function-item.html -- that's why specifying that generic in your fixed example ("if I insert an explicit cast to the function pointer type") since that coerces the zero-sized function item to a pointer-sized function pointer type.

  2. This probably could be improved diagnostically, mentioning that a function item type is not a function pointer. This is somewhat related to More distinctive pretty-printing of function item types #99927, but could probably use a special case in whatever code that prints " the trait MyTrait2<T> is implemented for fn(Box<T>) -> T". We should be careful to only mention this distinction when the impl's self type matches the signature of the function item, modulo any generic type substitutions.

@marienz
Copy link

marienz commented May 21, 2023

I spent a bunch of time banging my head against what looks like the same problem. Minimized example:

fn f1() {}

fn f2() {}

struct S {}

impl From<fn()> for S {
    fn from(f: fn()) -> S {
        S {}
    }
}

fn main() {
    // Example code did this, which works:
    let s1 = S::from(if true { f1 } else { f2 });
    // My code did this, which does not work:
    let s2 = S::from(f1);
    // I also tried this, which also does not work:
    let s3 = S::from(if true { f1 } else { f1 });
}

This gives the following error:

error[[E0277]](https://doc.rust-lang.org/stable/error_codes/E0277.html): the trait bound `S: From<fn() {f1}>` is not satisfied
  --> src/main.rs:15:22
   |
15 |     let s2 = S::from(f1);
   |              ------- ^^ the trait `From<fn() {f1}>` is not implemented for `S`
   |              |
   |              required by a bound introduced by this call
   |
   = help: the trait `From<fn()>` is implemented for `S`

(and the same for s3).

I think two things contributed to my confusion:

  • I only learned about "function items" after finding this bug report (that's probably More distinctive pretty-printing of function item types #99927). I was suspicious of the function-name-in-curly-braces in the type, but couldn't find the right search terms to figure out what they meant. Including "function item" or "fn item" in the error message would probably have helped. I checked the Rust book section on function pointers but it (probably correctly) doesn't mention function items, and I didn't know where to look in the reference.
  • It did not occur to me that returning different functions with the same signature from arms in an if might trigger a coercion that returning the same function does not. I wrote code like s2 based on code like s1 in a library example. I tried to make my failing code more like the working example by adding a conditional, but (because I didn't have a distinct function with the right signature handy) I returned the same one from both arms, ending up with s3, and went down the wrong path when that didn't work. That's probably just Rust inexperience that no single compiler diagnostic can fix, though :)

marienz added a commit to marienz/iced that referenced this issue May 21, 2023
...instead of just from function pointers.

I'm making this change not because I actually want to pass a closure,
but to make passing a single fixed function work. This commit also
simplifies the scrollable example slightly, and without the other half
of this change that simplified example fails to compile with:

```
error[E0277]: the trait bound `iced::theme::ProgressBar: From<for<'a> fn(&'a Theme) -> iced::widget::progress_bar::Appearance {progress_bar_custom_style}>` is not satisfied
   --> examples/scrollable/src/main.rs:292:28
    |
292 |                     .style(progress_bar_custom_style)
    |                      ----- ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<for<'a> fn(&'a Theme) -> iced::widget::progress_bar::Appearance {progress_bar_custom_style}>` is not implemented for `iced::theme::ProgressBar`
    |                      |
    |                      required by a bound introduced by this call
    |
    = help: the trait `From<for<'a> fn(&'a Theme) -> iced::widget::progress_bar::Appearance>` is implemented for `iced::theme::ProgressBar`
    = note: required for `for<'a> fn(&'a Theme) -> iced::widget::progress_bar::Appearance {progress_bar_custom_style}` to implement `Into<iced::theme::ProgressBar>`
note: required by a bound in `iced::widget::ProgressBar::<Renderer>::style`
   --> /home/marienz/src/iced/widget/src/progress_bar.rs:77:21
    |
77  |         style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `ProgressBar::<Renderer>::style`
```

This happens because `progress_bar_custom_style` by itself is a function
item, which is typically coerced to a function pointer when one is
needed, but not in this case. It is possible to work around this on the
caller's side, but especially since the compiler diagnostic for this is
a bit rough (see rust-lang/rust#100116) let's
try to make it work out of the box.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

4 participants