Home > Software design >  Implement generics for multiple types
Implement generics for multiple types

Time:09-30

I want to write some code like below:

use crate::{Ty1, Ty2};
struct Test<A, B> {
    ..
}
/// Match when A = Ty1 and B = Ty2
impl Test<Ty1, Ty2> {
    fn test() {
        ..
    }
}
/// Match all other cases
impl<?, ?> Test<?, ?> {
    fn test() {
        ..
    }
}

Of course it can be possible to implement all 4 cases manually, but I don't want to do. As far as I know, Rust doesn't support C -like specialization. So, how can I achieve it?

CodePudding user response:

As you correctly said, you can do it with unstable specialization feature.

The only option for stable is not to use generics here at all:

enum Ty {
  Ty1(Ty1),
  Ty2(Ty2),
}

struct Test {
  v1: Ty,
  v2: Ty,
}

impl Test {
  fn test(&self) {
    if let Ty1(v1) = self.v1 {
      if let Ty2(v2) = self.v2 {
        // specific logic
        return;
      }
    }
    // default logic
  }
}

CodePudding user response:

I tried to implement this by really tricky and limited way.. If somebody have any better idea, please suggest yours!

use std::marker::PhantomData;
/// Boolean trait for types.
trait Bool {}
struct Ty1 {}
struct NTy1 {}
impl Bool for Ty1 {}
impl Bool for NTy1 {}
struct Ty2 {}
struct NTy2 {}
impl Bool for Ty2 {}
impl Bool for NTy2 {}

struct True {}
struct False {}
/// And operator
struct And<A: Bool, B: Bool> {
    v: PhantomData<(A, B)>,
}
trait BoolOp {
    type Out;
}
/// Out is True when provided types are Ty1 and Ty2
impl BoolOp for And<Ty1, Ty2> {
    type Out = True;
}
/// Otherwise, the Out type is False
impl BoolOp for And<Ty1, NTy2> {
    type Out = False;
}
impl BoolOp for And<NTy1, Ty2> {
    type Out = False;
}
impl BoolOp for And<NTy1, NTy2> {
    type Out = False;
}

/// Helper trait for Test structure.
/// It has *real* implementation for *test* function.
trait TestHelperTrait {
    type ReturnType;
    fn test() -> Self::ReturnType;
}
/// C is the result of And<A, B>.
struct TestHelper<A, B, C> {
    x: PhantomData<(A, B, C)>,
}
/// When A = Ty1, B = Ty2, return i32
impl<A, B> TestHelperTrait for TestHelper<A, B, True> {
    type ReturnType = i32;
    fn test() -> Self::ReturnType {
        3
    }
}
/// Otherwise, return &str
impl<A, B> TestHelperTrait for TestHelper<A, B, False> {
    type ReturnType = &'static str;
    fn test() -> Self::ReturnType {
        "false"
    }
}

/// *Real* Test structure
struct Test<A, B> {
    x: PhantomData<(A, B)>,
}
impl<A: Bool, B: Bool> Test<A, B> {
    /// Exposed interface
    pub fn run(
        self,
    ) -> <TestHelper<A, B, <And<A, B> as BoolOp>::Out> as TestHelperTrait>::ReturnType
    where
        And<A, B>: BoolOp,
        TestHelper<A, B, <And<A, B> as BoolOp>::Out>: TestHelperTrait,
    {
        // Call helper's test function
        <TestHelper<A, B, <And<A, B> as BoolOp>::Out> as TestHelperTrait>::test()
    }
}

fn main() {
    let t1 = Test::<Ty1, Ty2> { x: PhantomData };
    let v1 = t1.run(); // i32

    let t2 = Test::<NTy1, Ty2> { x: PhantomData };
    let v2 = t2.run(); // &str

    let t3 = Test::<Ty1, NTy2> { x: PhantomData };
    let v3 = t3.run(); // &str

    let t4 = Test::<NTy1, NTy2> { x: PhantomData };
    let v4 = t4.run(); // &str
}

Playground

  • Related