genericsrustsyntaxtraitsunit-type

Why constrain the unit type by a generic trait bound in a where clause (as in `where () : Trait<…>`)?


Today I've encountered a somewhat weird syntax - where ():

fn hex_primary<Stream, Context>(stream: Stream) -> Parsed<u8, Stream, Context>
where
  (): IntRadixParse<Stream, Context, u8>,
{
  uint_radix(2, Radix::HEX).parse(stream)
}

To me it looks like "bound on unit type (aka empty tuple)", but I can't make sense of it. The () type doesn't implement every trait by default, does it? Alas, the official documentation is too vague and not complete enough (while being overly verbose), and I couldn't find anything related in it.

The original RFC for where clause also mentions this syntax, but without proper explanation:

fn increment<T>(c: T) -> T
    where () : Add<int,T,T>
{
    1 + c
}

But besides that, I know that such bounds can be specified not only in trait generics.
So what is it, when is it used, why is it needed and which problems does it solve?


Solution

  • In the code snippet, the where clause is used to narrow down the generic constraints. Instead of defining all the constraints, (): IntRadixParse<Stream, Context, u8> is used which means that whatever type Stream and Context have, they must follow the constraints of IntRadixParse.

    The trait IntRadixParse is

    pub trait IntRadixParse<Stream, Context, Token: 'static> = where
      Stream: Streaming,
      <Stream as Streaming>::Item: Into<u8>,
      Token: CheckedAdd + CheckedMul + CheckedSub + Zero + Copy + Debug,
      Context: Contexting<IntRadixAtom<Token>>,
      Context: Contexting<BaseAtom<u8>>,
      Context: Contexting<CoreAtom<Stream>>,
      Context: Contexting<UtilsAtom<Stream>>,
      u8: AsPrimitive<Token>;
    

    Note: It uses the trait alias unstable feature

    So to write the function without the weird unit constraint syntax, it would be something like

    fn hex_primary<Stream, Context>(stream: Stream) -> Parsed<u8, Stream, Context>
    where
      Stream: Streaming,
      <Stream as Streaming>::Item: Into<u8>,
      Context: Contexting<IntRadixAtom<u8>>,
      Context: Contexting<BaseAtom<u8>>,
      Context: Contexting<CoreAtom<Stream>>,
      Context: Contexting<UtilsAtom<Stream>>,
    {
      uint_radix(2, Radix::HEX).parse(stream)
    }
    

    Note: In the function Token was replaced with u8

    Another example:

    #![feature(trait_alias)]
    
    trait A<T> = where T: B; // Similar to `IntRadixParse` which
                             // shows that T should implement B
    
    /// trait B with a method a
    trait B {
        fn a(self);
    }
    
    /// implemented for unit
    impl B for () {
        fn a(self) {}
    }
    
    fn a<T>(a: T) where (): A<T> {
        a.a() // T gets inferred to have implemented B and `a()` can be called
    }
    
    fn main() {}