I really did my best to find a solution, but regrettably I have to ask for some help. I'd like to implement FromIterator accepting an iterator of tuples or references to tuples. A tuple has two elements. I'd like to have the first element being String, &String, &str.
So the compiler says: unconstrained type parameter. To be honest I feel like the type is constrained.
The generic parameter B is constrained by the item of the accepted iterator. So that says that the type of B will be the type of data in the input iterator.
The generic parameter S is constrained by the generic parameter B. So that says that the data will be known within the type of data in the input iterator.
"Unconstrained" doesn't mean what you think it does. It means that the type parameter S doesn't influence the signature of the implementation: since the impl is
impl FromIterator<B> for Node
there's nothing in this that would depend on S, only on B. Therefore, two choices of S would result in two, overlapping/ambiguous impls of the same trait (FromIterator<B>) for the same type (Node).
Anyway, your current code doesn't match your description of needing to accept tuples, but this does.
No, but that's a red herring. The AsRef<str> bound simply doesn't matter here at all. The point is that for two different concrete choices of S, eg. S = String and S = str, there would be two completely identical impls, which violates coherence.
Something wrong with my Rust today. How could they be identical?
I feel like I should get 6 different implementations according my code. Yours code works fine, but I need to understand the problem, rather than just pick a ready to use code.
That's not what I'm talking about at all. B doesn't depend on S in any way, but the constraint B: Borrow<S> can be fulfilled for many S. For example, picking B = String, one could pick two completely identical impls with different choices of S.
With the invented syntax Var = Type for denoting the substitution of concrete type Type for type variable Var, two such possible identical impls would be:
impl<S = String, B = String> FromIterator<B = String> for Node
where
S = String: AsRef<str>,
String: Borrow<S = String>,
and
impl<S = str, B = String> FromIterator<B = String> for Node
where
S = str: AsRef<str>,
String: Borrow<S = str>,
which are exactly the same impl, because both are impl FromIterator<String> for Node. The difference is only in what path Borrow and AsRef "go through", but that doesn't change the impl itself.
Consider the following user-defined types— There's no way for the compiler to decide between picking S=A and S=B if you pass an iterator of ABs to Node::from_iter, but the choice will change the result.
struct A(String);
struct B(String);
struct AB { a:(A,u64), b:(B,u64) };
impl AsRef<str> for A {
fn as_ref(&self)->&str { self.0.as_str() }
}
impl AsRef<str> for B {
fn as_ref(&self)->&str { self.0.as_str() }
}
impl Borrow<(A, u64)> for AB {
fn borrow(&self)->&(A, u64) { &self.a }
}
impl Borrow<(B, u64)> for AB {
fn borrow(&self)->&(B, u64) { &self.b }
}
Now I'm understanding the pseudocode. But I don't want to maintain two different implementations. I can use the function to share the functionality of the trait implementations, but it doesn't seem elegant.
Don't you have a more elegant idea?
Parameters of generic functions don't have to be constrained because you can specify them at the call[1] site. Example.
Parameters on implementations have to be constrained because there's no way to specify which implementation you want elsewhere (e.g. a call site), you can only specify which type and trait you're talking about. Therefore you have to be able to find the fully qualified implementation from the fully qualified type and a fully qualified trait.
An impl has to be selectable based on the types involved — <SomeTy<T...> as SomeTr<U...>>::func() must always determine a unique function — but there is no such rule for function generics. A function can even have completely unused type parameters, because it's always possible for the caller to specify explicitly which ones it means. (For example, std::mem::size_of::<T>() does not use T as input or output.) But there is no way to explicitly specify the type parameters of an impl, because impls are not things you name at the call site; they have to be unambiguous based only on the type and the trait.