What is the advantage of publishing a concrete type in a crate's API instead of `impl trait`?

Viewed 340

In patching a crate, I took it on myself to hide the internal iterator type, but the author says that publishing the type is a feature, and that best practice is to publish an explicit wrapper structure for every iterator exposed in the public API. Apparently, the Rust standard library does this for all of its iterators.

Why do this? More concretely, if implementing a type that's compatible with std::env::Args, what are the pros and cons of using...

// implement iterator compatible with std::env::Args
pub struct Args { // public
    // pub(crate) ...
}

impl Iterator for Args {
    // ...
}

pub fn args() -> Args {
    // ...
    // return Args
}

vs

// implement iterator compatible with std::env::Args
pub(crate) struct Args { // hidden (outside of crate)
    // pub(crate) ...
}

impl Iterator for Args {
    // ...
}

pub fn args() -> impl Iterator<Item = String> {
    // ...
    // return Args
}
1 Answers

There's never One True Best Practice.

Reasons for returning a concrete type include:

  1. You cannot currently declare a variable of type impl Trait, so any such usage has to be inferred. These cannot easily be placed in a struct for the same reason.

  2. You cannot add inherent methods to an impl Trait return type. For example, Chars has the as_str method.

  3. As trentcl points out, impl Trait cannot conditionally implement a trait. This is important for iterator adapters such as Skip.

  4. You state "compatible with std::env::Args", but here are the traits that Args implements:

    impl Iterator for Args {}
    impl ExactSizeIterator for Args {}
    impl DoubleEndedIterator for Args {}
    impl Debug for Args {}
    

    Your interface doesn't allow for 3 of the four of those. Consumers of your API cannot iterate from the back anymore, as one example. You can fix this by doing impl Iterator<Item = String> + DoubleEndedIterator + ExactSizeIterator + Debug, but at some point you effectively have your own type anyway. This problem is also possible if you return a newtype over an existing iterator, which is why I want a better delegation syntax.

See also the C-NEWTYPE-HIDE API guideline.

the Rust standard library does this for all of its iterators

The iterators in the standard library were created before impl Trait existed so they had no other choice. They cannot be changed now to no longer return a concrete type due to backwards compatibility.

Related