Changing one element of an array makes it not convert to slice

I have an array of two factory functions and a function that takes that array as a slice.
However if I change it so that the two elements are the same, it takes it as a reference to
an array instead of as a slice and gives and compile error. I suppose it has something to do with the {CheckerGradient::new} notation in the error message, but I don't understand the meaning of that.

	// uncommenting this line and commenting the other compiles fine:
    //let zero_arity_factories = [Gradient::new, CheckerGradient::new]; 
	
	// this line causes a compile error calling 'generate'
    let zero_arity_factories = [CheckerGradient::new, CheckerGradient::new]; 

    for i in 0..20 {
        let num_nodes = 8;
        let root = generate(num_nodes, &zero_arity_factories, &factories);
		...
	}
error[E0308]: mismatched types
   --> src/main.rs:835:40
    |
835 |         let root = generate(num_nodes, &zero_arity_factories, &factories);
    |                    --------            ^^^^^^^^^^^^^^^^^^^^^ expected slice, found array of 2 elements
    |                    |
    |                    arguments to this function are incorrect
    |
    = note: expected reference `&[fn() -> Box<(dyn ImgOp + 'static)>]`
               found reference `&[fn() -> Box<(dyn ImgOp + 'static)> {CheckerGradient::new}; 2]`

OK it seems that the type inference cannot generalize from a single instance of a thing. If I give the type explicitly, then it compiles.

    let zero_arity_factories: [fn() -> Box<(dyn ImgOp + 'static)>; 2] =
        [CheckerGradient::new, CheckerGradient::new];

Try...

I tried that.

error[E0308]: mismatched types
   --> src/main.rs:836:40
    |
836 |         let root = generate(num_nodes, &zero_arity_factories[..], &factories);
    |                    --------            ^^^^^^^^^^^^^^^^^^^^^^^^^ expected fn pointer, found fn item
    |                    |
    |                    arguments to this function are incorrect
    |
    = note: expected reference `&[fn() -> Box<(dyn ImgOp + 'static)>]`
               found reference `&[fn() -> Box<(dyn ImgOp + 'static)> {CheckerGradient::new}]`
note: function defined here
   --> src/main.rs:759:4
    |
759 | fn generate(n: usize, f0: &[ZeroFactory], f1: &[Factory]) -> BoxImgOp {
    |    ^^^^^^^^ --------  ------------------  --------------

For more information about this error, try `rustc --explain E0308`.

Ugh. You're going to make me undelete my post.

Yeah. I realized shortly after posting that I misunderstood the problem.

dropping the explicit lifetime also works:

let zero_arity_factories: [fn() -> Box<dyn ImgOp>; 2] =
        [CheckerGradient::new, CheckerGradient::new];
1 Like

Sorry about the undeleting, but I think it is an understandable misunderstanding, since it was the first thing I tried.

1 Like

This seems like the core of the issue, which is similar to issues #51233 and #71619.

I don't fully understand the difference between a fn pointer and a fn item, but type inference got stuck on the latter seemingly because it is contained inside another type (the array).

No worries. I was just hoping to slink away before anyone noticed.

This is essentially the same issue as in this recent post. Each function has its own unique unnameable type, which is known only to the compiler. The function type fn() -> Box<dyn ImgOp + 'static> is different from the type of the specific functions Checkergradient::new or Gradient::new, but the latter can be coerced into the former.

Type coercion means that, in certain contexts, if a type T is expected but type R is provided, and there is a coercion from T to R, then the code typechecks.

In the first case, all items of the array must have a common type T, but have in fact different function types. This makes the type checker search for a coercion to a common type, and it finds it: it is the function type fn() -> Box<dyn ImgOp + 'static>. Now the array is well-typed as [fn() -> Box<dyn ImgOp + 'static>; 2] which can dereference to &[fn() -> Box<dyn ImgOp + 'static>], and the code successfully typechecks.

In the second case, all elements of the array already have the same type, so its type is successfully inferred as [CheckerGradient::new; 2]. Note that once the type is fully inferred, it cannot be changed. So you pass &[CheckerGradient::new; 2] to a function expecting &[fn() -> Box<dyn ImgOp + 'static>], which means that this will typecheck only if the former coerces to the latter. But if you look at the list of valid coercions, there is nothing which would make it legal, which is exactly what the compiler complains about. Note that coercions are transitive, but there is no coercion [T; N] to [U; N], even if T coerces to U.

9 Likes