Help test impl Trait!

It’s finally coming! We’re in the home stretch for landing the long-awaited impl Trait feature. impl Trait allows functions to accept and return values of unnamed types that implement some trait.

Many longstanding bugs and ICEs have been fixed, and final syntax details are nearly resolved. With that in mind, we need help testing and experimenting with the feature before stabilizing. Please consider taking a moment or two to play around with the feature, perhaps by converting existing Rust libraries or projects to use impl Trait. To try it yourself, just update to the latest nightly and put #![feature(conservative_impl_trait, universal_impl_trait)] at the top of your crate, like this:

#![feature(conservative_impl_trait, universal_impl_trait)]

fn running_count(iter: impl Iterator<Item = u32>) -> impl Iterator<Item = u32> {
    let mut cnt = 0;
    iter.map(move |x| {
        cnt += x;
        cnt
    })
}

fn main() {
    for cnt in running_count(1..10) {
        println!("{}", cnt);
    }
}

If you have any question, concerns, or feedback about the feature, please message me on IRC, Gitter, or open an issue on rust-lang/rust and cc me (cramertj).

Known remaining issues:

20 Likes

Can closures be made generic?

fn running_count<F: FnOnce(impl Iterator<Item = u32>)>(f: F) {
    // …
}

Awesome! I can add it as a gated feature for one of my projects where I use Box. I’ll do some speed tests as well to see if this helps with the dynamic dispatch.

1 Like

impl Trait isn’t currently allowed in Fn syntax, no. We definitely want to support it eventually, but the final design is not yet resolved. It’s possible that we might want impl Trait in Fn() argument position to be sugar for type-HRTB (e.g. Fn(impl Debug) could desugar to for<T: Debug> Fn(T)). Type-HRTB doesn’t have an RFC and hasn’t been implemented, so there’s still a ways to go before we see anything like that on stable.

Makes sense, thanks!

Is there a version of the calendar example somewhere implemented using impl trait ? That’s actually what motivated its development (@eddyb 's calendar-driven-development!) and I’d like to see how it solves the problem that it was supposed to solve.

I’m excited to see this feature land, but I’m bemused by the example showing use of impl trait in argument position. This looks to be just be a third way of writing a trait bound, and one that would require rewriting to something totally syntactically different if you ever needed to name the parameter type. Have I misunderstood what this new syntax does?

Is there a version of the calendar example somewhere implemented using impl trait ?

I think this run-pass calendar test might be what you're looking for :slight_smile:

3 Likes

This looks to be just be a third way of writing a trait bound, and one that would require rewriting to something totally syntactically different if you ever needed to name the parameter type. Have I misunderstood what this new syntax does?

Argument-position impl Trait is a shorthand way to say "I want to take an input parameter of any type that implements Trait". As you said, fn foo(x: impl Trait) { ... } desugars roughly to fn foo<T: Trait>(x: T) { ... }. This change offers primarily ergonomic and learnability benefits-- for more on the motivation, see RFC 1951.

4 Likes

I would have found it more confusing when learning Rust if there was another, less powerful, very different looking, way of writing the same thing. It also feels like needless code churn - there’s no point in new syntax if no-one uses it, and if using it is encouraged then a load of existing code (and docs, blog posts, forum answers…) becomes “bad form” for using the other two of the three syntactic forms for the same thing.

Edit: this sounds more hostile than intended, largely due to terseness from typing it on a phone. I’ll not derail this thread any further - is there a proper place to raise this sort of objection or has that ship sailed?

3 Likes

I think that anonymous universal quantification on its own isn’t all that enticing, but with existentials it becomes just that.

fn process(src: impl Iterator<Item = u8>) -> impl Iterator<Item = u8> {
    ..
}

looks pretty good… There’s a natural progression to it =)

It’s also a matter of ergonomics, with anonymity, you don’t have to have type variables on one side and parameters after when you just want a value that satisfies a bound.

2 Likes

Does this include allowing impl Trait to be used in trait items? (IIRC that was an RFC that was accepted, right?) Also how does impl Trait interact as a type parameter in signatures? e.g. Could fn to_sql<W: Write>(&self, out: &mut Output<W, DB>>) be rewritten as fn to_sql(&self, out: &mut Output<impl Write, DB>)?

Does this include allowing impl Trait to be used in trait items? (IIRC that was an RFC that was accepted, right?)

There hasn't been an RFC yet for impl Trait in trait items. RFC 2071 allows you to accomplish something similar, but that RFC isn't yet implemented.

Also how does impl Trait interact as a type parameter in signatures?

Currently, using impl Trait makes it an error to manually specify type parameters for a function (a la collect::<Vec<_>>). This is a conservative restriction that we will almost certainly lift as we get more experience with the feature. IMO the most likely course of option is that impl Trait type parameters don't appear in parameter lists at all, but that would make it a breaking change to make the transition in your example.

Very excited about this! I have a quick question:

With the currently generic syntax, I can write a function that takes 2 arguments, and requires both arguments to be the same type:

// requires t and u to be the same type
fn bar<T, U>(t: T, u: T)
where T: Iterator<Item=u32> {}

Am I correct in thinking that there is no way to do this in the new impl Trait syntax? I’ll have to continue to use the old generic syntax?

// `t` and `u` are allowed to be different types
fn baz(t: impl Iterator<Item=u32>, u: impl Iterator<Item=u32>) {}

Am I correct in thinking that there is no way to do this in the new impl Trait syntax? I’ll have to continue to use the old generic syntax?

Yes, that's correct.

1 Like

Why following code does not compile:


#![feature(conservative_impl_trait, universal_impl_trait, dyn_trait)]

fn running_count(iter: impl Iterator) -> impl Iterator { iter }

fn main() {
    for cnt in running_count(1..10) {
        println!("{}", cnt);
    }
}

My thinking is that when I call running_count(1..10), it should be instantiated as

fn running_count(iter: Range<i32>) -> Range<i32>

then the code should compile.

Another question: can dyn and impl be used together?

// compile
fn t<T: Iterator + ?Sized>(t: &T) {}

// compile
fn t2(t: & impl Iterator + ?Sized) {}

// does not comile
fn t3(t: & dyn impl Iterator + ?Sized) {}

Is there a place I can see some before and after code with impl Trait?

1 Like

It doesn’t compile because the compiler doesn’t know that the iterator it returns is an iterator of i32s, just that its an iterator. So outside of that function, the compiler has no idea what type cnt is.

You can specify that its an iterator of i32s, and then it will work

fn running_count(iter: impl Iterator<Item = i32>) -> impl Iterator<Item = i32> { iter }

fn main() {
    for cnt in running_count(1..10) {
        println!("{}", cnt);
    }
}
``

Or, even better:

fn running_count<T>(iter: impl Iterator<Item = T>) -> impl Iterator<Item = T> { iter }

fn main() {
    for cnt in running_count(1..10) {
        println!("{}", cnt);
    }
}
3 Likes

Please consider the following usage case: play.

It would be great if impl Trait in argument position could be not only a syntactic sugar, but add something more useful to the language. If impl Trait would introduce hidden type parameter, send3_v1 and send3_v2 could be equivalent, but send3_v2 implementation is more straightforward to write:

    foo.send3_v1::<Bar1, Bar2, Bar3>(
        |r| r.bar1(),
        |r| r.bar2(),
        |r| r.bar3(),
    );
    foo.send3_v2::<Bar1, Bar2, Bar3, _, _, _>(
        |r| r.bar1(),
        |r| r.bar2(),
        |r| r.bar3(),
    );
1 Like