Home > OS >  Using Rust empty type as a generic bound
Using Rust empty type as a generic bound

Time:02-24

How do I constraint a Rust generic type to an empty type? I need to create a type with optional value, so that if the generic type is (), it does not use any memory. Here's a simple example - Data could be a i32, () (4 bytes) or it could be i32, i32 (8 bytes). There is an add() for both cases. I get this error (which makes sense, but not sure how to avoid it). I need to handle all valid numeric types.

error[E0119]: conflicting implementations of trait `std::ops::Add` for type `Data<(), ()>`
...
note: upstream crates may add a new impl of trait `num_traits::Num` for type `()` in future versions
use num_traits::Num;

trait MyNumericType: Num {}
impl<T: Num> MyNumericType for T {}

struct Data<T1: MyNumericType, T2> {
    a: T1,
    b: T2,
}

impl<T: MyNumericType> Add for Data<T, ()> {
    type Output = Self;

    fn add(self, other: Self) -> Self {
        Self {
            a: self.a   other.a,
            b: (),
        }
    }
}

impl<T: MyNumericType> Add for Data<T, T> {
    type Output = Self;

    fn add(self, other: Self) -> Self {
        Self {
            a: self.a   other.a,
            b: self.b   other.b,
        }
    }
}


impl<T1: MyNumericType,T2> Data<T1, T2>  {
    fn new(a: T1, b: T2) -> Self {
        Self { a, b }
    }
}

fn main() {
    let r1 = Data::new(1, 2)   Data::new(3, 4);
    let r2 = Data::new(1, ())   Data::new(2, ());
}

CodePudding user response:

The problem is that you don't control the () type, so someone could, completely unknown to you and with no malicious intent, write an impl that breaks your code. Rust forbids this kind of action at a distance.

Fortunately, () is a really simple type. So we can just make our own.

#[derive(Clone, Copy)]
struct MyUnit;

MyUnit is basically identical to () except that it's in your crate, not Rust's stdlib and not somebody else's crate. If someone comes along and writes an impl for MyUnit, then they must already have your crate as a dependency and thus are aware of the risks. Then just write your impls in terms of Data<T, MyUnit> instead of Data<T, ()>. It's still a zero-sized type and thus is subject to all of the same optimizations as ().

  • Related