Mark
Mark

Reputation: 19969

Expected output type changes when marking trait method and associated type as default for specialization

I want to implement a modulo operation for most Rem types in Rust:

#![feature(specialization)]

use std::ops::{Add, Rem};

/// Define a modulo operation, in the mathematical sense.
/// This differs from Rem because the result is always non-negative.
pub trait Modulo<T> {
    type Output;

    #[inline]
    fn modulo(self, other: T) -> Self::Output;
}

/// Implement modulo operation for types that implement Rem, Add and Clone.
// Add and Clone are needed to shift the value by U if it is below zero.
impl<U, T> Modulo<T> for U
    where
        T: Clone,
        U: Rem<T>,
        <U as Rem<T>>::Output: Add<T>,
        <<U as Rem<T>>::Output as Add<T>>::Output: Rem<T>
    {
    default type Output = <<<U as Rem<T>>::Output as Add<T>>::Output as Rem<T>>::Output;

    #[inline]
    default fn modulo(self, other: T) -> Self::Output {
        ((self % other.clone()) + other.clone()) % other
    }
}

This compiles fine without the defaults, but with the default, I get

error[E0308]: mismatched types
  --> main.rs:
   |
   |     default fn modulo(self, other: T) -> Self::Output {
   |                                          ------------ expected `<U as Modulo<T>>::Output` because of return type
   |         ((self % other.clone()) + other.clone()) % other
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected Modulo::Output, found std::ops::Rem::Output
   |
   = note: expected type `<U as Modulo<T>>::Output`
              found type `<<<U as std::ops::Rem<T>>::Output as std::ops::Add<T>>::Output as std::ops::Rem<T>>::Output`

I don't understand why that would happen. I need the defaults because I want to specialize it for Copy types.

I'm using Rust 1.29.0-nightly.

Upvotes: 1

Views: 157

Answers (1)

Shepmaster
Shepmaster

Reputation: 430791

Here is a smaller reproduction of the problem (an MCVE):

#![feature(specialization)]

trait Example {
    type Output;
    fn foo(&self) -> Self::Output;
}

impl<T> Example for T {
    default type Output = i32;

    default fn foo(&self) -> Self::Output {
        42
    }
}

fn main() {}

The problem arises because a specialization of this implementation can choose to specialize either Output or foo but it doesn't have to do both:

impl<T> Example for T
where
    T: Copy,
{
    type Output = bool;
}

In this case, the original implementation of foo would no longer make sense — it's not returning a value of type Self::Output anymore.

The current implementation of specialization requires you to think both locally and globally, which is the context you have to read the error message in. It's not ideal, but problems like this (and many more complicated things, I'm sure) are part of the reason that it isn't stable yet.

Upvotes: 1

Related Questions