Advanced type guessing

I have a macro which automatically generates a struct with varying generic parameters.

build_struct!(Name, [Opt1, Opt2, Opt3]);

will generate something like

struct Name<
    /* Generics of Opt1 */,
    /* Generics of Opt2 */,
    /* Generics of Opt3 */
> {
    ...// we do not worry about the fields.
    // but they also depend on [Opt1, Opt2, Opt3]
}

Later on I am using this struct in a generic function.

fn use_my_struct<N>() {...}
use_my_struct::<Name<_, _, _, ...>>();

This works when I do it manually since the compiler can infer all missing types but requires me to manually write down the _ placeholder for each generic type.
How can I write the last line of code without this? A "simple" fix would be to write another macro which simply returns the generics in the correct order but to avoid additional complexity, I would like to know if this is possible with a different language feature.

I can change the function signature of use_my_struct. So currently, my workaround looks like this:

fn use_my_struct<N>(&mut N) {...}
let _name = Name::default();
use_my_struct(&_name);

but this requires me to implement Default for Name which does not make sense at all in my case. Going away from the Default trait also only shifts the semantics of the underlying problem but does not fix it.

Like so?

// Literally all `_` but the compiler has enough information to fill them in
use_my_struct::<Name<_, _, _>>();

If yes, what is allowing the compiler to infer the correct types? (Perhaps that will hint at a way to avoid writing them out.)

Yes exactly like that.

Edit: The internals do not guess it, it is simply guessed later on.
The internals of the

fn use_my_struct<N>() {...}

function determine the generic parameters of Name<_, _, _>. This function is also generated by a macro and thus somehow "knows" which generics should be used in what context.

Again ... I can simply write a macro to generate the generics, but I would love to discover a new solution which is more elegant and less maintenance than an additional macro.

It can't be the internals; that's not how Rust works. It has to be a bound on the generics or something.

Something like this may work, but it's hard to say without more information.

Well ... my example is possibly incomplete and maybe this was misleading. It is possible to do something similar in Rust. See here: Rust Playground.

In my particular example, I have no trait bounds since the type is guessed later on. In fact by the same mechanism just shown. So you are right in that this initial function signature can not do it. I can see why this was confusing .

You need to copy the URL after clicking Share :slight_smile:

Sorry. Fixed it.

1 Like

In addition to the "add a method under Name<_>" idea, you could perhaps give the generic parameters default types.

The method idea works because you can elide type parameters in expressions (places where you would need turbofish).

Name::use_my_struct();

The default parameters work because you can elide them in type position (where you don't need turbofish).

use_my_struct::<Name>();

Another idea I had was to set up a type alias via a trait, but the compiler wasn't willing to infer through the extra redirection.

Another idea I had was to use TAIT somehow, but that's not yet available on stable.

This is really interesting but adding default types can have adverse effects if type guessing goes wrong later on. I would want to know if this was the case.

Unfortunately I am looking for solutions which are stable now.

Instead of an inherent method, you could wrap use_my_struct in a trait so it's not specific to Name...

// Apparently you have control of `use_my_struct` so you could just
// make this the only `use_my_struct`
impl<T: MyTrait<usize>> UseMyStruct for T {}
trait UseMyStruct: Sized + MyTrait<usize> {
    fn use_my_struct() {
        use_my_struct::<Self>()
    }
}

And then Name::use_my_struct() is usable like it was with an inherent method.


More along the lines of your OP, instead of Default, you could use your own trait and PhantomData, say. Example. (You said this only shifts the semantics of the problem without fixing it, but I don't know what you meant.)

This would probably fix it. But i have 2 structs which are generated. So maybe I need to implement on a tuple (Name1, Name2) instead. Not sure if the inherent method still works after doing that.

The main reason why I did not like the Default variant is because I actually need to instantiate this struct and define a Trait which bears meaning which both makes no sense in this case.
With "shifting the semantics" I meant that I could also do

impl Name {
    fn new() -> Self {...}
}

instead of using the Default trait.

But your solution does not use any instantiation and defining a custom trait may be an option after all. Although it is quite some boilerplate as well.

I will have a look at this again in the next days. Thanks for the many suggestions.

1 Like