Skip to content

Conversation

@joshchngs
Copy link
Contributor

The concrete Iterator type for all implementations of IntoEnumIterator is guaranteed to implement these traits, because the only implementor is the #[derive(EnumIter)] macro, which emits an impl for each for the generated {EnumType}Iter struct.

However, when using IntoEnumIterator as a generic constraint, the concrete type is not known, so the impl of these traits cannot be inferred by the type checker with out additional help.

Here are some examples, using Itertools::cartesian_product() as the motivator, because it requires Iterator + Clone:

// We know this function will work, but it fails to type check
// without these additional trait bounds on IntoEnumIterator.
pub fn example_broken<T: Clone + IntoEnumIterator, U: Clone + IntoEnumIterator>(
) -> impl Iterator<Item = (T, U)> + Clone {
    T::iter().cartesian_product(U::iter())
}
// It's possible to add where constraints at the use point to
// workaround the issue, without this change.
// This version will typecheck.
pub fn example_workaround<T: Clone + IntoEnumIterator, U: Clone + IntoEnumIterator>(
) -> impl Iterator<Item = (T, U)>
where
    <T as IntoEnumIterator>::Iterator: Clone,
    <U as IntoEnumIterator>::Iterator: Clone,
{
    T::iter().cartesian_product(U::iter())
}

Tested against Rust 1.52.1, 1.72.1, & 1.74.0

The concrete Iterator type for all implementations of
IntoEnumIterator is guaranteed to implement these traits, because
the only implementor is the `#[derive(EnumIter)]` macro, which
emits an impl for each for the generated {EnumType}Iter struct.

However, when using IntoEnumIterator as a generic constraint, the
concrete type is not known, so the impl of these traits cannot be
inferred by the type checker with out additional help.

Here are some examples, using Itertools::cartesian_product() as
the motivator, because it requires `Iterator + Clone`:

// We know this function will work, but it fails to type check
// without these additional trait bounds on IntoEnumIterator.
pub fn example_broken<T: Clone + IntoEnumIterator, U: Clone + IntoEnumIterator>(
) -> impl Iterator<Item = (T, U)> + Clone {
    T::iter().cartesian_product(U::iter())
}

// It's possible to add where constraints at the use point to
// workaround the issue, without this change.
// This version will typecheck.
pub fn example_workaround<T: Clone + IntoEnumIterator, U: Clone + IntoEnumIterator>(
) -> impl Iterator<Item = (T, U)>
where
    <T as IntoEnumIterator>::Iterator: Clone,
    <U as IntoEnumIterator>::Iterator: Clone,
{
    T::iter().cartesian_product(U::iter())
}
@Peternator7 Peternator7 merged commit ffe8873 into Peternator7:master Dec 3, 2023
@Peternator7
Copy link
Owner

Thanks for the PR, this is technically a breaking change if someone had manually implemented EnumIter, but I generally don't maintain back compat in those scenarios so this feels like a good change to me. Makes it easier to use the associated type :)

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants