Mismatched types: GAT compiler bug?

Hi all,

The code below is failing to compile on nightly:

#![feature(generic_associated_types)]
#![allow(type_alias_bounds)]

use once_cell::sync::Lazy;
use std::collections::HashMap;

// ============= generic code =============

trait Provider: Sized {
    // IterateCb's iterator item.
    type ListEntry<'a>: std::fmt::Debug;

    // Load IterateCb callbacks.
    fn load_callbacks() -> Callbacks<Self>;
}

type IterateCb<P: Provider> = for<'a> fn(&'a P) -> Box<dyn Iterator<Item = P::ListEntry<'a>> + 'a>;

struct Callbacks<P: Provider>(HashMap<&'static str, IterateCb<P>>);

// ============ implementation ============

// Global callbacks.
static CALLBACKS: Lazy<Callbacks<Instance>> = Lazy::new(Instance::load_callbacks);

struct Instance {
    peers: Vec<Peer>,
    routes: Vec<Route>,
}

#[derive(Debug)]
struct Peer(i32);

#[derive(Debug)]
struct Route(i32);

#[derive(Debug)]
enum InstanceListEntry<'a> {
    Peer(&'a Peer),
    Route(&'a Route),
}

impl Provider for Instance {
    type ListEntry<'a> = InstanceListEntry<'a>;

    fn load_callbacks() -> Callbacks<Self> {
        let mut callbacks = HashMap::new();

        // First callback.
        let callback: IterateCb<Self> = |instance| {
            let iter = instance.peers.iter().map(InstanceListEntry::Peer);
            Box::new(iter)
        };
        callbacks.insert("peers", callback);

        // Second callback.
        let callback: IterateCb<Self> = |instance| {
            let iter = instance.routes.iter().map(InstanceListEntry::Route);
            Box::new(iter)
        };
        callbacks.insert("routes", callback);

        // todo!();
        Callbacks(callbacks)
    }
}

fn main() {
    // Initialize instance.
    let instance = Instance {
        peers: vec![Peer(1), Peer(2)],
        routes: vec![Route(1), Route(2)],
    };

    // Print peers.
    if let Some(cb) = CALLBACKS.0.get("peers") {
        let list_iter = (*cb)(&instance);
        for list_entry in list_iter {
            println!("peer: {:?}", list_entry);
        }
    }
    // Print routes.
    if let Some(cb) = CALLBACKS.0.get("routes") {
        let list_iter = (*cb)(&instance);
        for list_entry in list_iter {
            println!("route: {:?}", list_entry);
        }
    }
}

Playground

In this program, I have a global list of callbacks that return an iterator over a generic associated type (Provider::ListEntry<'a>). The lifetime parameter is necessary because the associated type needs to be an enum of references.

Unfortunately the code isn't compiling, and the error message doesn't seem to make much sense:

# cargo check
error[E0308]: mismatched types
  --> src/main.rs:67:19
   |
67 |         Callbacks(callbacks)
   |                   ^^^^^^^^^ expected associated type, found enum `InstanceListEntry`
   |
   = note: expected struct `HashMap<&'static str, for<'a> fn(&'a Instance) -> Box<(dyn Iterator<for<'a> Item = <Instance as Provider>::ListEntry<'a>> + 'a)>>`
              found struct `HashMap<&str, for<'a> fn(&'a Instance) -> Box<(dyn Iterator<for<'a> Item = InstanceListEntry<'a>> + 'a)>>`
   = help: consider constraining the associated type `<Instance as Provider>::ListEntry<'a>` to `InstanceListEntry<'_>` or calling a method that returns `<Instance as Provider>::ListEntry<'a>`

I've asked for help on discord this morning and I was told this is likely a compiler bug related to GAT + trait objects. Before I proceed and file a bug report, could anyone else confirm that's indeed the case?

I apologize if my example program is too contrived and hard to make sense. The real program is an IP routing stack where I need a unified interface to fetch operational data from multiple protocols (e.g. RIP, OSPF). Each protocol implements the Provider trait and has a global list of callbacks of all kinds (IterateCb being the only one that is giving me problems).

Here's a workaround. I think you should file a report.

     fn load_callbacks() -> Callbacks<Self> {
-        let mut callbacks = HashMap::new();
+        let mut callbacks: Callbacks<Self> = Callbacks(HashMap::new());
 
         let callback: IterateCb<Self> = |instance| {
             let iter = instance.peers.iter().map(InstanceListEntry::Peer);
             Box::new(iter)
         };
-        callbacks.insert("peers", callback);
+        callbacks.0.insert("peers", callback);

         let callback: IterateCb<Self> = |instance| {
             let iter = instance.routes.iter().map(InstanceListEntry::Route);
             Box::new(iter)
         };
-        callbacks.insert("routes", callback);
+        callbacks.0.insert("routes", callback);

-        Callbacks(callbacks)
+        callbacks
     }
1 Like

Removing the : Callbacks<Self> annotation on the workaround results in a different set of similar errors when attempting to insert, which might be worth mentioning too.

Yes, definitely a compiler bug, as far as I can tell. Please feel free to open an issue about it.

Let me also note that I find the Iterator<for<'a> Item = …>> weird in the error message; but that could als be just an unrelated diagnostics issue.

1 Like

Thank you guys for confirming this is a bug, and for the handy workaround :slight_smile:

I should file a bug report once I get back home after Christmas.

Thanks again!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.