Search code examples
templatesgenericsrustnested

In Rust, which type declaration is closest to a type declaration nested in a generic class in C# or C++?


(This is the Rust version, for Scala version, please go to In Scala or other JVM language, which type declaration is closest to a type declaration nested in a generic class in C# or C++?)

When writing a library that relies heavily on generic/template in C# or C++, one trick I often use is to declare several structs that uses the same generic type arguments as nested types under a static class.

e.g. these declaration (using C# as an example):

    public struct Gen1<T1, T2, T3>
    {
    }

    public struct Gen2<T1, T2, T3>
    {
    }

is functionally equivalent to this:

    public static class Ts<T1, T2, T3>
    {
        public struct Gen1
        {
        }

        public struct Gen2
        {
        }
    }

Namely, Ts<int, int, string>.Gen1 is converted to a specialised class by the compiler, with its own static members. Ts<int, int, String> and Ts<int, int, int> becomes 2 different classes that shares nothing except names.

I have not found similar abstract in most other languages (the only exception is LEAN4, where the static generic class becomes a section: https://lean-lang.org/lean4/doc/sections.html). When converting such code to Rust, most LLMs will suggest using mod feature (which has no generic/template) and give an obviously defective answer, e.g. the following answer from Claude 3.5:

mod container<T> {
    pub struct Holder {
        value: T,
    }

    impl<T> Holder {
        pub fn new(value: T) -> Self {
            Holder { value }
        }

        pub fn get(&self) -> &T {
            &self.value
        }
    }
}

What's the easiest/shortest way to convert such declaration?


Solution

  • Rust does not support defining structs within structs (and yeah mod can't introduce generics). So the Rust equivalent would look like the first C# snippet; Gen1 and Gen2 need to be defined seperately with their own generic parameters.


    If you want Ts to have some strong association with Gen1 and Gen2, you can do that with an associated type declaration but it can only be through a trait implementation (until inherent associated traits are supported). Here's how that could look in Rust:

    struct TsGen1<T1, T2, T3> {
        // ... fields
    }
    
    struct TsGen2<T1, T2, T3> {
        // ... fields
    }
    
    struct Ts<T1, T2, T3> {
        // ... fields
    }
    
    trait Gen {
        type Gen1;
        type Gen2;
    }
    
    impl<T1, T2, T3> Gen for Ts<T1, T2, T3> {
        type Gen1 = TsGen1<T1, T2, T3>;
        type Gen2 = TsGen2<T1, T2, T3>;
    }
    

    Then you could refer to the "nested" types as Ts<A, B, C>::Gen1. Helpful if you want that organization, but not helpful if you're just trying to simplify definitions.