Naming convention : Vec<T> / [T; N]

In my API I have two version of a method: one that takes an Iterator and returns a Vec, and another one that takes and returns a slice of size N.

I have no clue what suffix to use to differentiate these methods. What is the naming convention in this case ?

fn eval(x: impl Iterator<Item = Foo>) -> Vec<Goo> {
    /* ... */
}

fn eval<const N: usize>(x: [Foo; N]) -> [Goo; N] {
    /* ... */
}

I don't think there is a naming convention for this particular case, but I would probably go with eval_iter and eval_array.

I would probably also use IntoIterator rather than Iterator.

7 Likes

I'd say the convention is to use the same name for both and have multiple implementations of a trait.

Having to remember different names for essentially the same thing is an additional burden on the user, so better avoid it if you can.

Something like this ?

struct Expr;

struct Foo;
struct Goo;

trait Eval<T> {
    type Output;
    
    fn eval(x: T) -> Self::Output;
}

impl<I> Eval<I> for Expr 
where I : IntoIterator<Item = Foo>
{
    type Output = Vec<Goo>;
    
    fn eval(x: I) -> Self::Output {
        todo!()
    }
}
impl<const N: usize> Eval<[Foo; N]> for Expr 
{
    type Output = [Goo; N];
    
    fn eval(x: [Foo; N]) -> Self::Output {
        todo!()
    }
}

playground

Unfortunately there is a conflict implementation:

note: upstream crates may add a new impl of trait `std::iter::Iterator` for type `[Foo; _]` in future versions

I don't agree that the convention is to use a trait for this. Sure, people do that sometimes, but using two different functions with different names is also very common. I would not bother with traits for this case — that is over-complicating it.

(In fact, I don't think it's possible due to conflicting implementations in this case. But I probably still wouldn't in this case even if it was possible.)

5 Likes

Directly implementing traits for iterator is probably not possible here.

But your first function looks very similar to the standard library function collect, just with some processing added to turn a Foo into a Goo. The second function takes a slice as an input, which can also be converted into an iterator. To unify the two functions it remains to produce the output. Maybe you can specify that as a type parameter like for collect (and use collect?).

[T; N] is an array, not a slice. [T] is a slice. Sometimes references to slices are also called slices.

You would need specialization for a trait [1].


  1. or negative trait bounds and an implementation of !Iterator for arrays; they already implement IntoIterator ↩ī¸Ž

2 Likes

That is interesting; maybe the compiler could conclude that since there's an impl<I: Iterator> IntoIterator for I there can't be a type that impls both so the above is valid?

I guess that will over-complicate the coherence rules, though...

There is a type that impls both, precisely because of this blanket impl. There can't be a type that explicitly equals both, but for the sake of "impl existence" blanket and explicit implementations are probably indistinguishable.

2 Likes

That is an interesting observation, but I think it ultimately boils down to something like disjoint associated types and negative reasoning more generally.

To sketch how I made that jump: if you have a

mod stdlib {
    trait Foo {}
    trait Bar {}
    impl<T: ?Sized> Bar for T where T: Foo {}
}

struct MyType;
impl Bar for MyType {}

And then later you

 struct MyType;
-impl Bar for MyType {}
+impl Foo for MyType {}

That can be a non-breaking change (if the blanket impl which now supplies your Bar impl is the same).

It doesn't apply to the Iterator/IntoIterator case, because for it to be non-breaking you would have to be going from

impl IntoIterator for MyType {
    type IntoIter /*: [implements] Iterator] */ = Self;
    // ...
}

But for the compiler to figure this out, we're way into "coherence is comparing types to each other" territory.

I think you can do the non-breaking-due-to-blanket-impl change for From/Into, but didn't actually go confirm that.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.