6

I am working with some generic structs like the following:

pub struct Example<A, B, C, D, E, F> {
    inner: OtherExample<A, B, C, D, E, F>
    ...
}

and throughout implementing methods of this struct, I have to keep referring to that huge set of types like so:

impl<A, B, C, D, E, F> Example<A, B, C, D, E, F> {
    pub fn get_inner(&self) -> &OtherExample<A, B, C, D, E, F> { 
        &self.inner
    }
    ...
}

and I'm wondering if there is a way to shorten up the notation on all these generic types. For the sake of readability, I can't just use single letters like I have in the examples above, so I would really want to create a generic type alias in the struct like so:

pub struct Example<AliasedGenerics = <A, B, C, D, E, F>> {
    inner: OtherExample<AliasedGenerics>
    ...
}

impl<AliasedGenerics = <A, B, C, D, E, F>> Example<AliasedGenerics> {
    pub fn get_inner(&self) -> &OtherExample<AliasedGenerics> {
        &self.inner
    }
    ...
}

So that I don't have to keep writing extremely long lines and improve the readability of the generic implementation.

ChosunOne
  • 684
  • 8
  • 26
  • 1
    This looks like a lexical substitution. Building a dedicated macro could certainly help. – prog-fh Dec 17 '21 at 06:49
  • 1
    Does this answer your question? https://stackoverflow.com/questions/70378140/is-it-possible-to-combine-type-constraints-in-rust – Joe_Jingyu Dec 17 '21 at 08:06
  • @Joe_Jingyu Not quite, since what I have here are a bunch of type specifications, not trait specifications. – ChosunOne Dec 17 '21 at 08:25
  • When you have a large number of generics, it is typically a sign that you could use associated types instead. Of course, maybe in you case you do need all the separate generics, but if you're not familiar with associated types, look them up, they might be exactly what you need. In that case both `Example` and `OtherExample` would be generic over one type, constrained by a trait that declares (and whose implementors provide) all the other needed types. – user4815162342 Dec 17 '21 at 11:10

1 Answers1

7

Without knowing more context, I can't say whether this will solve your problem, but you can use associated types to do something rather similar:

// Define the "alias"
pub trait GenericsSet { type A; type B; type C; }

pub struct Example<G: GenericsSet>(OtherExample<G>);
pub struct OtherExample<G: GenericsSet> { a: G::A, b: G::B, c: G::C, }

// I was a bit surprised that it is possible,
// but it seems you can even impose limits on some of the parameters
impl<G: GenericsSet<C = String>> Example<G> {
    fn foo(&self) -> &str { &self.0.c }
    fn get_inner(&self) -> &OtherExample<G> { &self.0 }
}

// Here, the illusion fades a bit: 
// You'll need a type for each combination of types you want to use
struct MyGS1 {}
impl GenericsSet for MyGS1 { type A = (); type B = bool; type C = String; }

fn main() {
    println!("{}", Example::<MyGS1>(OtherExample {a: (), b: true, c: "works".into() }).foo())
}

Playground

I would recommend against using this trick in a public API, though.

Caesar
  • 6,733
  • 4
  • 38
  • 44
  • I don't know whether to be awed or disgusted, but it answers the question concisely and correctly. Wow, thank you. – ChosunOne Dec 17 '21 at 11:38
  • 2
    I'd go for disgusted. I really don't like macros, but I think they're preferable here. – Caesar Dec 17 '21 at 14:41
  • 1
    The issue with macros is that my IDE loses all type hints inside a macro definition, so your solution is more type-safe as far as my IDE is concerned (CLion). If I change the types I need somewhere, I'll be stopped before I try to build the project. – ChosunOne Dec 17 '21 at 15:42