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

Fn item/fn pointer distinction breaks fn pointer generics #20178

Closed
pmsanford opened this issue Dec 23, 2014 · 6 comments · Fixed by #20415
Closed

Fn item/fn pointer distinction breaks fn pointer generics #20178

pmsanford opened this issue Dec 23, 2014 · 6 comments · Fixed by #20415

Comments

@pmsanford
Copy link

This is broken now:

fn accepts_optional_fn_ptr(_test: Option<fn(int) -> int>) {}

fn test_int(_: int) -> int { 0i }

fn main() {
    accepts_optional_fn_ptr(Some(test_int));
}

because it expects Option<fn(int) -> int> and gets Option<fn(int) -> int {test_int}>

This is an issue for FFI specifically because Option<fn(...) -> ...> is the idiomatic way (mentioned in the FFI guide) to represent C function pointers as it takes advantage of nullable pointer optimization.

I'll add that for anyone encountering this issue, an explicit cast fixes it:

accepts_optional_fn_ptr(Some(test_int as fn(int) -> int));
@alexcrichton
Copy link
Member

This is actually expected fallout from #19891, and you can patch it up with:

fn accepts_optional_fn_ptr(_test: Option<fn(int) -> int>) {}

fn test_int(_: int) -> int { 0i }

fn main() {
    accepts_optional_fn_ptr(Some(test_int as fn(int) -> int));
}

It is unfortunate, however, that the coercions don't happen deeply through Some.

cc @nikomatsakis

@pmsanford
Copy link
Author

@alexcrichton yea, I updated my issue to reflect that. The problem I'm reporting is really one of ergonomics. Adding explicit casts everywhere is a little messy especially if the callbacks have lots of arguments. Then you start adding newtypes for each callback...

Even something like an .as_ptr method on fn items would be fine, in my opinion. I was going to try and look into adding that after the holidays if this isn't addressed in some other way by then.

infinityb added a commit to infinityb/dbus-rs that referenced this issue Dec 24, 2014
fn item/fn pointer are now different things.
see: rust-lang/rust#20178

Derefing on MessageItems doesn't work anymore.
@eddyb
Copy link
Member

eddyb commented Dec 31, 2014

The real issue is a more subtle one, as the following program compiles:

fn accepts_optional_fn_ptr(_test: Option<fn(int) -> int>) {}

fn test_int(_: int) -> int { 0i }

fn main() {
    accepts_optional_fn_ptr(Some::<fn(_) -> _>(test_int));
}

As it turns out, we propagate expected types down, we do coercions inside of function calls, but we don't do something which I have no idea what to call so I'm just going to name it "polymorphic unpacking".
When we encounter Some in the top-down traversal, we see that it is a path requiriing one type parameter, which we treat as if it were provided with ::<_>, i.e. it is left to be inferred.
Thus, Some(test_int) becomes Some<#0>(test_int) and the argument is expected to be #0 - which isn't going to trigger a coercion or anything, just assign #0 = typeof test_int.
By the time we look at the return type, it is too late: there is no coercion, the actual type is Option<typeof test_int> and that isn't going to match Option<fn(int) -> int>.
However, if we tried to combine the two pieces of information that we have, Option<fn(int) -> int> and Option<#0>, we could deduce that #0 = fn(int) -> int quite early, and everything would follow as it should.

As I was writing this, @nikomatsakis has provided me with a straight-forward way of doing this. I also realized that my original plan of extracting fn(int) -> int for the argument's expected type without actually assigning #0 is just roundabout, i.e. it would force #0 to be fn(int) -> int eventually anyways, and it's easier to just unify them up-front.

@chris-morgan
Copy link
Member

More things are affected by this; here’s another example, one which #20415 does not appear to have fixed:

fn a() { }
fn b() { }

fn main() {
    let c = if true { a } else { b };
    c();
}
k.rs:5:13: 5:37 error: if and else have incompatible types: expected `fn() {a}`, found `fn() {b}` (expected fn item, found a different fn item)
k.rs:5     let c = if true { a } else { b };
                   ^~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to previous error

Is this a separate issue? Should I file it as a new issue?

@huonw
Copy link
Member

huonw commented Jan 12, 2015

Separate issue please (I believe that sort of situation where the compiler has to implicitly construct some "supertype" without any type annotations is harder).

@Ryman
Copy link
Contributor

Ryman commented Feb 25, 2015

@chris-morgan Did you file an additional issue for that? (Can't find one via search)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
6 participants