Search code examples
rusttypescompilationparameter-passing

Expected type parameter, found opaque type


I have a function which returns an impl Stream that I want to call from a method in another trait. Here's a very rough example of what I'm trying to accomplish:

struct Example<S>
where
    S: Stream<Item = String>,
{
    phantom: std::marker::PhantomData<S>,
}

trait Thing<S> {
    fn go(self) -> S;
}

impl<S> Thing<S> for Example<S>
where
    S: Stream<Item = String>,
{
    fn go(self) -> S {
        the_stream()
    }
}

fn the_stream() -> impl Stream<Item = String> {
    stream::unfold(0, |i| async move { Some((i.to_string(), i + 1)) })
}

This produces the error:

expected type parameter S found opaque type impl Stream<Item = String>

The type parameter S has the same constraints as the return type of the_stream but the compiler is unable to match them up. I'm clearly missing some understanding of how opaque types can map to type parameters. Is there something I can do to help the compiler out and get the code running without boxing the stream?


Solution

  • While they both implement the same trait the types S and impl Stream<Item = String> are in fact different types. With a generic type parameter the caller of the function can determine what concrete type they want. But with an opaque type parameter they can't specify that, all the user knows is that there exists some type that implements it. Consider this example:

    use std::collections::HashSet;
    fn generic<A: FromIterator<u8>>() -> A {
        (1..9).collect()
    }
    
    fn existential() -> impl FromIterator<u8> + std::fmt::Debug {
        (1..9).collect::<Vec<_>>()
    }
    
    fn main() {
        let v: Vec<_> = generic();
        let s: HashSet<_> = generic();
        dbg!(v, s);
        let e = existential();
        dbg!(e);
    }
    

    The function generic can return any type that implements FromIterator<u8> we want, the existential one always produces a Vec<u8> but we can't even assume that, all we can use are the traits we specify namely FromIterator<u8> which doesn't really let us do anything with the type. For demonstration purposes I also added the Debug trait so we can inspect the output of existential

    There is no way to define generic in terms of existential because existential can only ever return the same type while generic has to produce the requested type.