Using macros to accomplish trait aliasing?

Hello,

I've been struggling to find an elegant way to define a closure signature in one place, and then use it as part of the signature for multiple functions, allowing the argument types to be inferred automatically when the functions are called.

Along these lines:

trait MyClosure<OneT, TwoT> : Fn(&OneT, &[TwoT]) {}

fn fancy_func<ClosureT : MyClosure<usize, usize>>(closure : ClosureT) {
    closure(&1, &[1, 2, 3]);
}

fn main() {
    fancy_func(|my_index, my_slice| println!("{}", my_slice[*my_index]));
}

My failures with trait bounds are documented here: Clean way to declare stand-alone closure - #3 by LukeTPeterson

What I thought I needed was trait aliasing, but it appears trait aliasing breaks type inference. In addition to being experimental.

So I thought, maybe I can use a macro. I just need to replace a particular string with another string as a compile pre-process. That's fundamentally all.

But, It seems there are limited places where declarative macros can be invoked. i.e. I can't figure out how to embed them into function signatures.

In the past, when I've felt like I'm fighting the language like this, it's usually because I'm fundamentally doing something I'm not supposed to be doing. But there must be a nice way to declare closure / function signatures as types or or traits with generalizable parameters, without being forced to make them boxed objects.

Thank you in advance for any insight.

You're missing this (not that it solves your problem, but without it the call is not possible):

impl<T, U, V> MyClosure<U, V> for T where T: Fn(&U, &[V]) {}

I suspect (but am not 100% certain) you just have too many inference-blocking intermediate traits... like Deref. Check out the similarities between your example and one using Box (try removing the Box annotation), and how a version with less indirection succeeds without annotation.

2 Likes

Wow! I didn't realize "Inference-blocking" was a thing traits could do. But your example certainly proves the point.

Thank you for the answer!

Unfortunately I'm not sure what to do with the newly-gained knowledge because the complicated type signatures of the closure are the whole reason I want to factor them out into their own trait / type. I don't think it's possible for me to simplify them the way you did, for the actual code I'm working with.

It feels like a reasonable compromise is type aliasing:

pub type MyClosure<'a, OneT, TwoT : num_traits::Bounded> = &'a dyn Fn(&OneT, &[TwoT]);
fn fancy_func(closure : MyClosure<usize, usize>) {
    closure(&1, &[1, 2, 3]);
}

fn main() {
    fancy_func(&|my_index, my_slice| println!("{}", my_slice[*my_index]));
}

But it has two downsides:

  1. you must call it with a ref to the closure instead of the closure itself (minor annoyance because it breaks convention)
  2. type aliasing doesn't let you bound the incoming types. (potentially seriously limiting)

How do others solve these issues?

Thank you again.

I'm not exactly sure what the tipping point is, in part because there is a lot of "magic" (intrinsics) in your use case (Deref, closures, Fn traits). Maybe a future resolver would be up for it. If I use the Fn bound directly it works, so there's something going on with the indirection through the custom trait with a supertrait.

I got it down to two places where you have to put the type signatures.

1 Like

Thank you! Your work-around is very interesting. I'll try to figure out if I can adapt it to the real use-case.

If I use the Fn bound directly it works, so there's something going on with the indirection through the custom trait with a supertrait.

Right. Somewhere in the trait-supertrait or trait-alias boundary, type inferencing is lost, but as you demonstrated, only in some situations.

My default assumption, and it's almost always accurate, is that the underlying software is doing the right thing and I'm just using it wrong. But is it possible this is an edge-case bug in Rust? This test, from the trait_alias RFC discussion, makes me think perhaps. Rust Playground

Thanks again for all your help and insights.

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.