Enforcing generics to be of the same type

Friends

I have this code I am working on:

enum ContinuousPortType {
	 Integer,
	 Decimal,
	 Float,
}

struct ContinuousControlPort<T>
{
	 max: T,
	 min: T,
	 default: T,
	 logarithmic: bool,
}

I would like to make sure that T when used in ContinuousControlPort is the same in all instantiations. That is the type of min is exactly the same as the type of max where both are, say ContinuousPortType::Integer.

Perhaps I have completely the wrong design, but before I give up and use a different design, is there a way of making this work?

Before anything else can be discussed we need some question that makes some kind of sense.

Because currently your questions sounds like an attempt to guarantee that colors are either all square or all triangular. IOW: something that doesn't make any sense.

But ContinuousPortType::Integer is not a type. It's enum value and for this very reason it can not be related to T.

Worse: they don't even exist simultaneously: T exist during compilation of your program and, notable, doesn't exist when it runs. While ContinuousPortType::Integer only exist at runtime[1]

Maybe you may describe higher-level goal that you are trying to achieve?


  1. With const genrics it may actually exist both during compilation time, but it's not entirely the same thing as the one that exist at runtime. ↩ī¸Ž

I'm guessing that Integer, Decimal and Float will actually have values attached. And as @khimru said, enum variants are not types, so these need to be structs. And T in ContinuousControlPort needs a trait bound in order to constrain the type of its fields.

The way I know how to do it is something like this (I've added a sub-module to make the naming less redundant):

mod continuous_port {
    pub trait Type {}

    pub struct Integer(pub i64);
    pub struct Decimal(pub i64, pub u8);
    pub struct Float(pub f64);

    impl Type for Integer {}
    impl Type for Decimal {}
    impl Type for Float {}
}

struct ContinuousControlPort<T: continuous_port::Type> {
    max: T,
    min: T,
    default: T,
    logarithmic: bool,
}

let cp = ContinuousControlPort {
    min: continuous_port::Integer(10),
    max: continuous_port::Integer(20),
    default: continuous_port::Integer(10),
    logarithmic: false,
};

Edit: fixed module naming.


Or if you don't want to wrap the built-in integer and float types in a struct:

mod continuous_port {
    pub trait Type {}

    impl Type for i64 {}
    impl Type for (i64, u8) {}
    impl Type for f64 {}
}

struct ContinuousControlPort<T: continuous_port::Type> {
    max: T,
    min: T,
    default: T,
    logarithmic: bool,
}

let cp: ContinuousControlPort<i64> = ContinuousControlPort {
    min: 10,
    max: 20,
    default: 10,
    logarithmic: false,
};

Or the same as above but without the sub-module:

trait ContinuousPortType {}

impl ContinuousPortType for i64 {}
impl ContinuousPortType for (i64, u8) {}
impl ContinuousPortType for f64 {}

struct ContinuousControlPort<T: ContinuousPortType> {
    max: T,
    min: T,
    default: T,
    logarithmic: bool,
}

let cp = ContinuousControlPort::<i64> {
    min: 10,
    max: 20,
    default: 10,
    logarithmic: false,
};
2 Likes

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.