You'll probably find http://discuss.rust-lang.org/ a better place to discuss ideas for the language.
Cheers! Nick. On Sun, Oct 12, 2014 at 10:44 PM, Eduardo León <[email protected]> wrote: > Hello: > > First of all, I am really excited by the addition of associated types to > Rust as an experimental feature. This feature has the potential to reduce > much of the boilerplate that comes with the use of generic traits, which I > use extensively in my code. > > However, in the exact form they are proposed: > > * > https://github.com/rust-lang/rfcs/blob/d2c2f0f524df814d7b38f69311ab67f41c2ec3ec/active/0059-associated-items.md > * https://github.com/rust-lang/rfcs/issues/313 > > ... associated types do not afford me much convenience. To illustrate > this, I will use an example. Consider the following generic trait: > > > pub enum GetResult<I, E, L> { > > Cont(E, I), > > Done(L) > > } > > > > pub trait InputIterator<I, E, L> : Copy { > > fn get(self, I) -> GetResult<I, E, L>; > > } > > This trait is implemented for some really scary types. One of the tamest > implementations in my code is: > > > pub struct Zip<L, R> { left: L, right: R } > > > > pub enum ZipInputLeftovers<LI, LE, LL, RI, RE, RL> { > > MoreL(LE, LI, RL), > > MoreR(LL, RE, RI), > > Neither(LL, RL) > > } > > > > impl<LI, LE, LL, L: InputIterator<LI, LE, LL> > > RI, RE, RL, R: InputIterator<RI, RE, RL>> > > > > InputIterator< (LI, RI), (LE, RE), > > ZipInputLeftovers<LI, LE, LL, RI, RE, RL> > > > for Zip<L, R> { > > > > fn get(self, (li, ri): (LI, RI)) -> > > GetResult< (LI, RI), (LE, RE), > > ZipInputLeftovers<LI, LE, LL, > > RI, RE, RL> > { > > match (self.left.get(li), self.right.get(ri)) { > > // ... > > } > > } > > } > > With associated types, this code can be rewritten this way: > > > pub trait InputIterator : Copy { > > type Iterator; > > type Element; > > type Leftovers; > > > > fn get(self, I) -> Get<Iterator, Element, Leftovers>; > > } > > > > impl<L: InputIterator, R: InputIterator> InputIterator for Zip<L, R> { > > type Iterator = (L::Iterator, R::Iterator); > > type Element = (L::Element, R::Element); > > type Leftovers = ZipInputLeftovers< > > L::Iterator, L::Element, L::Leftovers, > > R::Iterator, R::Element, R::Leftovers >; > > > > fn get(self, (li, ri): Iterator) -> > > GetResult<Iterator, Element, Leftovers> { > > match (self.left.get(li), self.right.get(ri)) { > > // ... > > } > > } > > } > > There are two remaining annoyances with this code: > > 1. We still have generics with long parameter lists: GetResult has 3 type > parameters, ZipInputLeftovers has 6 type parameters, other fancier > iterators have associated types with more than 10 (!) type parameters. > > 2. The type system permits the construction of nonsensical values, such as > Cont("hello", "world") [of type GetResult<&'static str, &'static str, T>] > or MoreL("ok", "now", "bye") [of type ZipInputLeftovers<&'static str, > &'static str, S, T, U, &'static str>]. In this case, the error is that the > type of "world" and "now", &'static str, is not a meaningful iterator type. > > I propose that the aforementioned issues can be avoided by allowing > generative type definitions (that is, structs and enums) inside of traits > and impls, rather than only allowing type synonyms: > > > pub trait InputIterator : Copy { > > type Iterator; > > type Element; > > type Leftovers; > > > > enum Result { > > Cont(Element, Iterator), > > Done(Leftovers) > > } > > > > fn get(self, Iterator) -> Result; > > } > > > > impl<L: InputIterator, R: InputIterator> InputIterator for Zip<L, R> { > > type Iterator = (L::Iterator, R::Iterator); > > type Element = (L::Element, R::Element); > > > > enum Leftovers { > > MoreL(L::Element, L::Iterator, R::Leftovers), > > MoreR(L::Leftovers, R::Element, R::Iterator), > > Neither(L::Leftovers, R::Leftovers) > > }; > > > > fn get(self, (li, ri): Iterator) -> Result { > > match (self.left.get(li), self.right.get(ri)) { > > // ... > > } > > } > > } > > The benefits of this change are tangible: > > 1. No more humongous generic parameter lists. > > 2. No nonsensical Result or Leftovers values can be constructed. > > An important observation is that overriding generative associated type > definitions should be disallowed. If we allow InputIterator implementations > to override the Result type, then we cannot pattern match on Result's > constructors from external code: > > > pub struct Transform<I, O> { input: I, output: O } > > > > impl<I: Source, O: Sink> Transform<I, O> { > > pub type Mapping = |I::Element| -> O::Element; > > > > pub enum Result { > > StopI(I::Leftovers, O::Iterator), > > StopO(I::Iterator, O::Leftovers) > > } > > > > pub fn exec(self, f: Mapping, i: I::Iterator, o: O::Iterator) -> > Result { > > match self.input.get(i) { > > I::Done(l) => StopI(l, o), > > I::Cont(e, i) => > > match self.output.put(o, f(e)) { > > O::Done(l) => StopO(i, l), > > O::Cont(o) => self.exec(f, i, o) > > } > > } > > } > > } > > Do you guys think associated structs and enums could make it into the > language? > > All these code snippets are taken from > https://github.com/eduardoleon/rust-stl/tree/master/src/stream . > > -- > Eduardo León > > _______________________________________________ > Rust-dev mailing list > [email protected] > https://mail.mozilla.org/listinfo/rust-dev > >
_______________________________________________ Rust-dev mailing list [email protected] https://mail.mozilla.org/listinfo/rust-dev
