Creating reference to Fn leads to error: types differ in mutability

Inspired by https://github.com/fitzgen/dodrio, I decided to try building something similar. Unfortunately, I have hit a snag. The following (simplified) code:

use bumpalo::Bump;

struct Node;

struct Ctx<'a> {
    bump: &'a Bump
}

type Callback = dyn for<'a> Fn(&mut Ctx<'a>) -> &'a [Node];

fn main() {
    let bump = Bump::new();
    let ctx = Ctx {
        bump: &bump,
    };
    let callback: &Callback = ctx.bump.alloc(|ctx: &mut Ctx| {
        ctx.bump.alloc([])
    });
}

leads to

error[E0271]: type mismatch resolving `for<'r, 'a> <[closure@src/main.rs:16:46: 18:6] as std::ops::FnOnce<(&'r mut Ctx<'a>,)>>::Output == &'a [Node]`
  --> src/main.rs:16:31
   |
16 |       let callback: &Callback = ctx.bump.alloc(|ctx: &mut Ctx| {
   |  _______________________________^
17 | |         ctx.bump.alloc([])
18 | |     });
   | |______^ types differ in mutability
   |
   = note: expected type `&mut [_; 0]`
              found type `&[Node]`
   = note: required for the cast to the object type `dyn for<'r, 'a> std::ops::Fn(&'r mut Ctx<'a>) -> &'a [Node]`

I don't believe the error message is correct because using a Box compiles just fine:

...
    let callback: Box<Callback> = Box::new(|ctx: &mut Ctx| {
        ctx.bump.alloc([])
    });
...

Using a Box could be a possible workaround, but I would like to uncover the underlying issue as to why the initial code doesn't work (and why the error message is wrong).

In your type decleration, you have:

type Callback = dyn for<'a> Fn(&mut Ctx<'a>) -> &'a [Node];

Notice, you have an immutable return for a slice of Node's.

Try making this:

type Callback = dyn for<'a> Fn(&mut Ctx<'a>) -> &'a mut [Node];

Unfortunately, that leads to

error[E0271]: type mismatch resolving `for<'r, 'a> <[closure@src/main.rs:16:46: 18:6] as std::ops::FnOnce<(&'r mut Ctx<'a>,)>>::Output == &'a mut [Node]`
  --> src/main.rs:16:31
   |
16 |       let callback: &Callback = ctx.bump.alloc(|ctx: &mut Ctx| {
   |  _______________________________^
17 | |         ctx.bump.alloc([])
18 | |     });
   | |______^ expected array of 0 elements, found slice
   |
   = note: expected type `&mut [_; 0]`
              found type `&mut [Node]`
   = note: required for the cast to the object type `dyn for<'r, 'a> std::ops::Fn(&'r mut Ctx<'a>) -> &'a mut [Node]`

I don't believe the issue is with the declaration (since the Box solution works).

Ah, yes, okay that makes sense. Because [T] is a dynamically sized type wherein you don't know how large it may be. Try looking into adding a ?Sized bounds somewhere therein. Check up the guide on dynamically sized types (DST's)

type Callback<T: ?Sized> = dyn for<'a> Fn(&mut Ctx<'a>) -> &'a mut [T];

Thanks for the bearing with me! But, unfortunately, that solution leads to

error[E0271]: type mismatch resolving `for<'r, 'a> <[closure@src/main.rs:16:52: 18:6] as std::ops::FnOnce<(&'r mut Ctx<'a>,)>>::Output == &'a mut [Node]`
  --> src/main.rs:16:37
   |
16 |       let callback: &Callback<Node> = ctx.bump.alloc(|ctx: &mut Ctx| {
   |  _____________________________________^
17 | |         ctx.bump.alloc([])
18 | |     });
   | |______^ expected array of 0 elements, found slice
   |
   = note: expected type `&mut [_; 0]`
              found type `&mut [Node]`
   = note: required for the cast to the object type `dyn for<'r, 'a> std::ops::Fn(&'r mut Ctx<'a>) -> &'a mut [Node]`

The is a learning process for all involved! I'm intrigued to find the solution too because this looks like something I might use in the future.

Have you considered, perhaps, making a wrapper of sorts like:

pub struct NodeArrayWrapper<'a> {
    inner: &'a [Node]
}

and then:

type Callback = dyn for<'a> Fn(&mut Ctx<'a>) -> &'a mut NodeArrayWrapper<'a>;

Further down the rabbit hole we go! That leads to a new issue:

error[E0271]: type mismatch resolving `for<'r, 'a> <[closure@src/main.rs:20:46: 24:6] as std::ops::FnOnce<(&'r mut Ctx<'a>,)>>::Output == &'a mut NodeArrayWrapper<'a>`
  --> src/main.rs:20:31
   |
20 |       let callback: &Callback = ctx.bump.alloc(|ctx: &mut Ctx| {
   |  _______________________________^
21 | |         ctx.bump.alloc(NodeArrayWrapper {
22 | |             inner: ctx.bump.alloc([]),
23 | |         })
24 | |     });
   | |______^ expected bound lifetime parameter 'a, found concrete lifetime
   |
   = note: required for the cast to the object type `dyn for<'r, 'a> std::ops::Fn(&'r mut Ctx<'a>) -> &'a mut NodeArrayWrapper<'a>`
1 Like

You can make it work by having a &mut Callback:

let callback: &mut Callback = ctx.bump.alloc(|ctx: &mut Ctx| {
    ctx.bump.alloc([])
});

The compiler can't seem to be able to infer the type and do the coercion &mut -> &, I'm not sure why yet.

3 Likes