Newbie Q: Function/API design recommendations

I'm trying to learn about what's the best way of making input parameters flexible.

I have a struct:

pub struct Example {
    ...
}

that I'd like to initialise from a collection of elements, for example a sequence of PathBuf's.

I don't want to hard-code the construction to a Vec parameter like

impl Example {
  pub fn new(files: Vec<PathBuf>) -> Self {
   ...
  }
}

What is the best practice to construct Example in this case? Implement From trait, or have new() take IntoIterator, or combination of both or something completely different?

If there are multiple options how can I determine which one to choose in which case?

I've read Rust API Guidelines: Flexibility but I'm not sure whether it applies to constructing new structs as well.

A flexible option is to accept impl IntoIterator<Item = impl Into<PathBuf>>, i.e. any object that can be converted to an iterator with item type convertible to PathBuf. See playground.

1 Like

If you're storing Vec<PathBuf> in the struct, then taking Vec<PathBuf> isn't too bad — at least guarantees no copying. You could make it slightly flexible with impl Into<Vec<PathBuf>>.

Interestingly, impl IntoIterator<Item=PathBuf> has a specialized implementation of .into_iter().collect() that compiles down to nothing if you pass in Vec<PathBuf>, so that's fine as an arg, too.

2 Likes

Thank you @Riateche and @kornel! It makes more sense to me now. The playground link was very useful.

@kornel the bit about into_iter().collect() implementation detail is good to know. Is it something that's a consequence how things are implemented or is it a deliberate specialisation? I've checked the docs and I'm not sure I can see this mentioned anywhere though I'm sure I've just been looking at wrong place.

libstd uses specializations for vec and slice a lot, but I haven't checked this one in libstd, only looked at output on rust.godbolt.org