I have used the IntoIterator also (here) to make a function accept a "list" of values, so I can either pass vec![x, y, z] or [x, y, z], etc.
I guess Into<String> can be used to denote any type that is convertible into a String (and might be the right choice here), but there is also ToString which has slightly different semantics, I believe.
Note that you might want to add extra bounds, such as I added ExactSizeIterator, which allows implementations to know the length without having to iterate through all values first.
Even if ExactSizeIterator can't be trusted in unsafe code, it still may help implementations to allocate memory more efficiently, e.g. by internally using
let some_vec = Vec::with_capacity(items.len())
which doesn't work if you omit the ExactSizeIterator trait bound.
Also note that using IntoIterator takes away some more abilities from the callercallee. In particular: you can't use all methods that are supported when working with slices. Also note that using generics may either lead to monomorphization overhead in regard to the size of the compiled binary or require an extra indirection (by making an inner function).
The code you came up with looks very reasonable to me. IntoIterator bounds allow for generally very flexible usage. One example you didn't show was the ability to also accept references such as &Vec<String> or &[String] with this bound. Unfortunately, &[&str] would not work due to a lack of String: From<&&str> implementations.
It even optimizes nicely in certain use-cases; e.g. if you give it a Vec<String>, I wouldn't be surprised if the specialized collect implementations in the standard library together with LLVM manage to optimize the whole into_vec_str conversion into a no-op.
This would be an advantage of FromStr; but if you want to be more restrictive than that, and the &&str use-cases come up a lot (and the workaround of doing .iter().copied() on the caller-site is too tedious), there’s always also the alternative to define a custom trait, which gives even more control what exact types are allowed; e.g. Into<String> is also something that char implements, which may or may not be a type you want to support. ↩︎
If it’s just about being more efficient, it might be enough to work with the Iterator::size_hint information, which is something that e.g. .collect::<Vec<_>>() will do to avoid unnecessary re-allocations.
Yeah, it does… it’s a bit tedious to actually find the source for it, because there’s an impressive number of layers of specialization layered on top of it , including – among other things – the one I hinted at that allows .collect to re-use a Vec’s allocation that was used to create the iterator in the first place that you’re collecting. But yes, (the lower-bound of) size_hintis indeed used for it.
I feel like using generics to perform type-conversions is a bad idea overall, but I'm not totally sure. I feel like (most of the times) it's best to do it on the caller side, for clarity (and also due to some implementation issues like possible monomorphization overhead, clunky syntax in the function definition, etc). But not sure.
Note that I feel strongest about impl Into<String> in particular. Other things might be more reasonable, especially if it's impl CustomTrait, and there's something to be said for impl Into<Cow<'_, str>> sometimes, etc.
But yeah, the "I'm making compilation slower and my binary bigger just to avoid writing a & in the caller" usually seems like the wrong tradeoff to me.
Thank you, that makes total sense in case it's just impl Into<String>. But what's your take on more complicated types like Vec<String> I started from? Would you still suggest moving my_str_slice.iter().map(Into::into).collect() into caller? Even for libs?
It might help if you said more about why you want to do this. What are you planning on doing with that Vec, say?
If that's allocating a new String for every single thing in the iterator, probably yes. That's quite an expensive thing to hide in a function call.
So I'd consider two things:
If I really do always need owned Strings, I'd take impl IntoIterator<Item = String>. Then it's up to the caller to make the strings -- and see the cost of that -- but it's also trivial to pass a Vec<String> if they happen to have one already. (And I can actually collect that back to a Vec<String> in O(1) today, thanks to some non-guaranteed specializations.)
If I don't actually need ownership, then I'd take something like impl IntoIterator<Item = impl AsRef<str>> instead. That way people can pass &[&str] or &[String] or many other such things, but I'm not doing any copying of it, so the flexibility isn't hiding a bunch of cost.