Unwritable but inferable type?

In the following snippet, the &i8 type annotation in the type of failing does not have the agreement of the compiler. An _ in its place does. Why? And what would be the correct type in that place?

use std::ops::{AddAssign, SubAssign};

fn main() {
    //let failing: fn(&mut i8, _) = if true {
    let failing: fn(&mut i8, &i8) = if true {
        SubAssign::sub_assign
    } else {
        AddAssign::add_assign
    };
    let mut n = 0i8;
    failing(&mut n, &0i8);
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
 --> src/main.rs:6:9
  |
6 |         SubAssign::sub_assign
  |         ^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
  |
  = note: expected fn pointer `for<'r, 's> fn(&'r mut i8, &'s i8)`
             found fn pointer `for<'r> fn(&'r mut i8, &i8)`

error[E0308]: mismatched types
 --> src/main.rs:8:9
  |
8 |         AddAssign::add_assign
  |         ^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
  |
  = note: expected fn pointer `for<'r, 's> fn(&'r mut i8, &'s i8)`
             found fn pointer `for<'r> fn(&'r mut i8, &i8)`

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground`

To learn more, run the command again with --verbose.

2 Likes

This is because the SubAssign function itself is not generic over a lifetime - the trait is just implemented generically over a lifetime (impl<'a> SubAssign<&'a i8> for i8). So there is no function that takes an &i8 with any lifetime and subtracts it from an i8, but instead there is a trait impl that takes an &i8 with any lifetime and subtracts it from an i8. So this code does compile because it specifies a single lifetime to use.

3 Likes

Note that e.g. this does not work:

// does not compile
use std::ops::{AddAssign, SubAssign};

fn a() {
    let failing: fn(&mut i8, _) = if true {
        SubAssign::sub_assign
    } else {
        AddAssign::add_assign
    };
    let mut n = 0i8;
    let mut m = 1i8;
    failing(&mut n, &m);
    failing(&mut m, &n);
    failing(&mut n, &m);
    failing(&mut m, &n);
}

exactly for the reason @Kestrer mentioned (add_assign and sub_assign aren’t generic over both arguments.

Neither can any workaround presented in previous comments handle this situation:

// does not compile
use std::ops::{AddAssign, SubAssign};

type Sig<'a, 'b> = fn(&'a mut i8, &'b i8);

fn main() {
    let failing: Sig = if true {
        SubAssign::sub_assign
    } else {
        AddAssign::add_assign
    };
    let mut n = 0i8;
    let mut m = 1i8;
    failing(&mut n, &m);
    failing(&mut m, &n);
    failing(&mut n, &m);
    failing(&mut m, &n);
}
// does not compile
use std::ops::{AddAssign, SubAssign};

fn a<'a>() {
    //let failing: fn(&mut i8, _) = if true {
    let failing: fn(&mut i8, &'a i8) = if true {
        SubAssign::sub_assign
    } else {
        AddAssign::add_assign
    };
    let mut n = 0i8;
    let mut m = 1i8;
    failing(&mut n, &m);
    failing(&mut m, &n);
    failing(&mut n, &m);
    failing(&mut m, &n);
}
// not even this works:
use std::ops::{AddAssign, SubAssign};

fn a<'a>() {
    //let failing: fn(&mut i8, _) = if true {
    let failing: fn(&mut i8, &'a i8) = if true {
        SubAssign::sub_assign
    } else {
        AddAssign::add_assign
    };
    let mut n = 0i8;
    let m = 1i8;
    failing(&mut n, &m);
}

The cleanest solution is thus (IMO) to simply apply some eta-expansion:

// this works
use std::ops::{AddAssign, SubAssign};

fn main() {
    let failing: fn(&mut i8, &i8) = if true {
        |x,y| SubAssign::sub_assign(x,y)
    } else {
        |x,y| AddAssign::add_assign(x,y)
    };
    let mut n = 0i8;
    let mut m = 1i8;
    failing(&mut n, &m);
    failing(&mut m, &n);
    failing(&mut n, &m);
    failing(&mut m, &n);
}

(or of course, using the operators:)

// even cleaner
fn main() {
    let failing: fn(&mut i8, &i8) = if true {
        |x,y| *x -= y
    } else {
        |x,y| *x += y
    };
    let mut n = 0i8;
    let mut m = 1i8;
    failing(&mut n, &m);
    failing(&mut m, &n);
    failing(&mut n, &m);
    failing(&mut m, &n);
}
5 Likes