My goal is to have a struct that represents a newtyped sequence of strings. Let's say CatFullName and DogFullName, so I don't accidentally pass one when the other is needed. I want to these to be usable both in an ownership and reference scenario. From other posts, I gather the way to do this is just to wrap a generic type, and then for each function or impl block, use a trait bound to say what you need the type parameter to be:
The question is, for a function that wants to use reference semantics, and just wants to look at a sequence of &strs without storing them, what should the bound on "P" be? Between Iterator, IntoIterator, [T], &[T], &[&T], str, &str, AsRef<str>..., my head is spinning.
Here's an attempt:
use itertools::Itertools; // 0.9.0
struct CatFullName<P>(P);
impl <P> CatFullName<P> {
fn to_string<S>(self) -> String
where S : AsRef<str>, P : IntoIterator<Item = S> {
self.0.into_iter().intersperse(" ").collect()
}
}
First of t, I'd avoid using generics in this way. Instead, copy the standard library pattern of Path and PathBuf and just define two types. However, regarding your code, I'd start but putting the constraint on the impl, not the fn, and then you need to use the trait itself in your code.
Note: I'm on my phone pushing a swing, so I haven't tested the above.
I'd add that having IntoIterator as the bound here largely defeats the purpose of allowing the open of either ownership or reference, since self is always consumed.
You'd need to call .map(|s| s.as_ref()) before intersperse() to make both operate on &str. However, that probably won't work with owning iterators, since there won't be anything storing the s for s.as_ref() to live (it's not possible to have a reference without an owned counterpart).
You can create a good'ol loop that pushes to a String.
You don't need unsafe for this. The reason std uses unsafe is to make converting from an OsStr to Path a zero-cost operation. The representation for OsStr has a lot of constraints due to the way they are consumed by the OS and that's what the unsafe is actually there for.
What about using CatFullName(Vec<String>) and CatFullNameRef<'a>(Vec<&'a str>)? That's effectively what you were trying to write with the generics.
If the double-indirection due to a Vec<String> is a performance issue for you (it's probably not, benchmark first), you can use something like the smol_str crate to store the string's contents inline (if it's less than 22 bytes long). Otherwise you can take a leaf out of PathBuf and Path's book and use a single string (either String or str) where each component is separated by a well-known delimiter (/ on Unices and \ or / on Windows) and iterating over segments is a case of string.split('/').
Well, there's also the fact that PathBuf can be turned into &Path, which I don't see how to do without unsafe (because who owns the Path? Technically, I don't think anyone does).
As far as I can tell, the main problem is that intersperse requires the two interspersed thing to be the same type as the iterated type, while creating a String with different printable things shouldn't be a problem.