rust-lang/rust

Specialization works only if type annotation is provided

kdy1 opened this issue · 1 comments

kdy1 commented

Playground link: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=a26a9e2af3acda5b9911458a6f54a72d

This trait implementation

impl<T, V, O, E> Validate<[T]> for V
where
    Self: Validate<T, Output = Result<O, E>>,
{
    type Output = Result<Vec<O>, E>;

    fn validate(&mut self, nodes: &[T]) -> Self::Output {
        nodes.iter().map(|node| self.validate(node)).collect()
    }
}

does not like a method defined as

impl Analyzer {
    /// Validates and store errors if required.
    pub fn check<T, O>(&mut self, node: &T) -> Option<O>
    where
        Self: Validate<T, Output = Result<O, Error>>,
    {
        let res: Result<O, _> = self.validate(node);
        match res {
            Ok(v) => Some(v),
            Err(..) => {
                // handle error
                None
            }
        }
    }
}

However, it works if type annotation is provided

fn main() {
    let mut a = Analyzer;
    let expr = Expr;
    // Uncomment this to see impl for [T] explodes
    // a.check(&expr);

    // This works without error
    a.check::<Expr, ()>(&expr);
}

further isolated:

as soon as any specialization exists, type inference coerces everything thats covered by the blanket default impl to that specialization, regardless of compatibility. workaround is to avoid type inference via explicit type annotation

#![feature(min_specialization)]


trait Consume<T> {
	fn consume(_: T);
}


//blanket default impl without any specializations
struct Consumer1;

impl<T> Consume<T> for Consumer1 {
	default fn consume(_: T) {
		//...
	}
}


//blanket default impl with 1 specialization
struct Consumer2;

impl<T> Consume<T> for Consumer2 {
	default fn consume(_: T) {
		//...
	}
}

impl Consume<i32> for Consumer2 {
	fn consume(_: i32) {
		//...
	}
}


fn main() {
	Consumer1::consume(true); //ok
	Consumer1::consume(42);   //ok

	Consumer2::consume(true); //error: expected `i32`, found `bool`
	Consumer2::consume(42);   //ok

	//workaround:
	<Consumer2 as Consume<bool>>::consume(true); //ok
}