Nesting trait implementors with differing concrete associated types

A project I have is exploring an alternate design for Rust's Error trait, in particular relating to error_generic_member_access. The basic idea is to define the trait like this (omitting less-relevant parts):

trait Error: ... {
    type Details;

    fn visit(...) -> ...;
    fn details(&self) -> Self::Details;
}

This falls apart when you have, for example, two errors with different concrete types for Error::Details where one of them was caused by the other and you try to visit the trees of errors to generate a report (i.e. print out the errors and their causes recursively). In an attempt to remedy this, I introduced a type called MapDetails that wraps the original error and contains a user-provided function that takes the original value and converts it into the desired type.

And now for the actual part I'd like help with: I'm hitting type-level infinite recursion with this approach and don't really know where to go from here. Example error message:

error[E0275]: overflow evaluating the requirement `&&Main: Debug`
  |
  = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`xtask`)
  = note: required for `&&&Main` to implement `Debug`
  = note: 128 redundant requirements hidden
  = note: required for `MapDetails<&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&..., ...>` to implement `Debug`
  = note: the full name for the type has been written to '[redacted]'
  = note: consider using `--verbose` to print the full type name to the console

Am I just getting trolled by autoref somewhere? Is this code something that the compiler should accept but currently doesn't? Is this genuinely invalid code due to infinite type-level recursion despite there definitely being a base-case at least at the value-level? If so, are there any other approaches I could try to get around this?

Code that implements/reproduces this is here, at this specific branch/commit: https://gitlab.computer.surgery/charles/derail/-/tree/41bd5202a6aebc0433a89e34f0e7e6c4cbe72eb9. The commit immediately preceding this one works fine, so the compiler error is introduced by the linked commit. Running cargo test should be sufficient to cause the compiler error.

(If the "trees of errors" thing sounds interesting, that part at least is working and has been published to crates.io as derail.)

Thanks!

I don’t have time to dig into it, but in my experience, the most common cause of infinite type-level recursion with references is a recursive function call like this:

fn recursive_callback_fn<F: Fn()>(func: F) {
    if false {
        recursive_callback_fn(&func);
    }
}

fn main() {
    recursive_callback_fn(|| {});
}

The problem here is that each recursion changes the function type from F to &F, &F to &&F, and so on. To avoid this problem, you need to change the function to take a &F so that it can be reborrowed as the same type (modulo lifetimes):

fn recursive_callback_fn<F: Fn()>(func: &F) {
    if false {
        recursive_callback_fn(func);
    }
}

pub fn main() {
    recursive_callback_fn(&|| {});
}

Of course, in your case the problematic generic might not be implementing Fn, but the antipattern of borrowing instead of reborrowing is something you should look out for.

(If you can't avoid borrowing, then you may need to introduce some dyn to make the recursion dynamic instead of statically infinite.)

1 Like

Yep, that was exactly the problem, thank you! I had actually tried moving towards taking references like in your example previously and it didn't help, but it turns out that was just because I didn't try hard enough I guess. Diff that fixes compilation is now here: https://gitlab.computer.surgery/charles/derail/-/commit/1ed76e7e37a59266660ffc1aa218312194a1fb64.

(Unfortunately, certain tests are now failing because they're overflowing the stack, but hopefully I can figure that out with a debugger and enough time. So much for "there definitely being a base-case at the value-level" lol. (4 hours later edit: figured it out!))