Where clause that specifies IntoIterator

I have responses from different data sources. Each data source must "promise" to host what I need to build a collection of files. To express this promise, given:

  1. Response - generic R
  2. Iterator with Item: File
  3. Collection

I do not expect the Response to be the Iterator. I expect it to

impl Files {
   pub(crate) fn new<R>(kind: Kind, resp: R) -> Files
where
   R: IntoIterator,
   todo!(),
{
   Files { 
     ...
     files: todo!() // instantiate using resp
   }
}

Given IntoIterator:

pub trait IntoIterator {
    type Item;
    type IntoIter: Iterator;
    fn into_iter(self) -> Self::IntoIter;
}

How do I specify the type Item: File in the where clause?

(as a bit of an aside, the iterator will use a move semantic)

Something like this should work:

impl Files {
   pub(crate) fn new<R>(kind: Kind, resp: R) -> Files
where
   R: IntoIterator<Item=File>,
{
   Files { 
     ...
     files: resp.into_iter().collect() // instantiate using resp
   }
}

@2e71828 How about how to express File as a generic that implements Into<File>?... I suspect there is no way around a resp with two generic parameters?

files: resp.into_iter().map(Into::into).collect() // instantiate using resp

Try:

where
   R: IntoIterator,
   <R as IntoIterator>::Item: Into<File>
1 Like

Wonderful.

I suspect in Haskell the concept is "functional dependency" in a multi-parameter type class. The syntax is something like c -> e when specifying the type class. It reads "e is uniquely determined by c".

When thinking through the trait spec, I was hanging on the fact that in order to instantiate the File (item), the compiler won't know the concrete type ?? -> File. The expression needs to convince the compiler that the input is any one type that implements Into<File>...?

That’s roughly the role of associated types in Rust: A type can have at most one implementation of IntoIterator, and each IntoIterator implementation has exactly one associated Item type. So, <T as IntoIterator>::Item necessarily refers to at most one concrete type.

3 Likes

Haskell also has associated type families. Here’s a (rough) equivalent of the traits involved, and ultimately the trait bound in question

{-# LANGUAGE TypeFamilies #-}

import Data.List

-- trait definitions
class Iterator self where
  type IteratorItem self
  next :: self -> Maybe (IteratorItem self, self)

class
  ( Iterator (IntoIter self)
  , IteratorItem (IntoIter self) ~ IntoIteratorItem self
  ) => IntoIterator self where
    type IntoIter self
    type IntoIteratorItem self
    intoIter :: self -> IntoIter self

class Into target self where
    into :: self -> target

-- unrelated demonstration
collect :: IntoIterator t => t -> [IntoIteratorItem t]
collect = unfoldr next . intoIter

-- mandatory main on godbolt.org
main = return ()

-- main contents
{-
// Rust code for comparison
struct Files;
struct File;
struct Kind;

impl Files {
    fn new<R>(kind: Kind, resp: R) -> Files
    where
        R: IntoIterator,
        <R as IntoIterator>::Item: Into<File>,
    {
        unimplemented!()
    }
}
-}

data Files = Files
data File = File
data Kind = Kind

newFiles ::
  ( IntoIterator r
  , Into File (IntoIteratorItem r)
  ) => Kind -> r -> Files
newFiles = undefined

Compiler Explorer

And here’s the “equivalent” using functional dependencies

{-# LANGUAGE FunctionalDependencies #-}

import Data.List

-- trait definitions
class Iterator item self | self -> item where
  next :: self -> Maybe (item, self)

class Iterator item intoIter
  => IntoIterator item intoIter self | self -> item intoIter where
  intoIter :: self -> intoIter

class Into target self where
    into :: self -> target

-- unrelated demonstration
collect :: IntoIterator item intoIter t => t -> [item]
collect = unfoldr next . intoIter

-- mandatory main on godbolt.org
main = return ()

-- main contents
{-
// Rust code for comparison
struct Files;
struct File;
struct Kind;

impl Files {
    fn new<R>(kind: Kind, resp: R) -> Files
    where
        R: IntoIterator,
        <R as IntoIterator>::Item: Into<File>,
    {
        unimplemented!()
    }
}
-}

data Files = Files
data File = File
data Kind = Kind

newFiles ::
  ( IntoIterator item intoIter r
  , Into File item
  ) => Kind -> r -> Files
newFiles = undefined

Compiler Explorer

1 Like

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.