4 ms·
I didn't really understand the Java comparison. As far as I understand, the Rust `Iter` trait is essentially a generic interface with one type parameter, the ty
by tsimionescu 2y ago
I didn't really understand the Java comparison. As far as I understand, the Rust `Iter` trait is essentially a generic interface with one type parameter, the type implementing the interface. If that understanding is correct, than this is essentially identical to Java's Iterator<T> interface, except that Java interfaces are not implicitly generic on the type that implements them. But in both cases, the Iterator has one and only one generic type parameter.
In Java, if Iterator had been a non-generic interface, then Vector<A>.Iterator.Next() would have had to return an Object instead of an A, as would all other implementations of Iterator for any collection type. Even if you defined a member type, Iterator.Result, it would still be the exact same type for all Iterator implementations.
The whole thing about dynamic vs static dispatch seems to be a bit of an unnecessary detour. I'm not even 100% sure that in practice Java would rely on dynamic dispatch to find the right method there, it may well be that it can be optimized to do static dispatch (the bigger help there is that all Java objects have the same size on the stack, sizeof(pointer), so a single method can always handle any object, whereas in Rust you need different methods for different objects regardless of logic).
- mmoskal 2y agoIn Java you can't have a class implement both Iterator<Foo> and Iterator<Bar> (type erasure; or maybe my Java is out of date...). Anyway if you could, the for loop would not know which to use. In Rust you could have both Iterators if it had a type parameter.
- withoutboats3 2y agoYes, this is the actual reason. In Java you're restricted to one implementation of an interface for a type by syntactic construction (classes list their interfaces in their header and each interface can only appear once). In Rust there is a similar restriction (called coherence), but it takes into account all of the parameters to a trait, including its generics. An illustrative example of the difference: `AsRef<T>` and `Deref` have almost identical signatures, except that the target type for `AsRef` is a parameter and for `Deref` is an associated type. `String` implements `AsRef<str>`, `AsRef<Path>`, and so on, but only `Deref<Target = str>`. The blog post's meandering description the difference between static and dynamic dispatch has no relevance whatsoever.
- tsimionescu 2y agoSo basically, Java's generic interfaces are equivalent to Rust's traits with associated types, and Java has no equivalent for a Rust trait with type parameters, at least in terms of this part of the language. I'm not sure how or if Rust traits can be used as function parameters - can I have a function that takes a parameter of type Iter? Can it take any implementation of Iter than, regardless of what item type that returns? How would I define a function that only accepts iterators that return, say, i32 items?
- AlotOfReading 2y agoYou'd write it as a type parameter with a type bound. There's a couple of different ways to write it, but one example: fn foo<I>(it: I) where I : Iterator<Item = i32> {}
- LegionMammal978 2y ago> can I have a function that takes a parameter of type Iter? fn example<I: Iterator>(iter: I) { ... } // or: fn example<I>(iter: I) where I: Iterator { ... } // or: fn example(iter: impl Iterator) { ... } > Can it take any implementation of Iter than, regardless of what item type that returns? Yes, in the above example. Then, the item type can be referred to as "<I as Iterator>::Item", or "I::Item" for short, unless you use the "impl Trait" syntax. You can also give the item type a name by giving it its own type parameter: fn example<T, I: Iterator<Item = T>>(iter: I) { ... } > How would I define a function that only accepts iterators that return, say, i32 items? fn example<I: Iterator<Item = i32>>(iter: I) { ... } // etc., just write Iterator<Item = i32> in place of Iterator
- Someone 2y agoDoes that matter for Iterator? You can have class FooIterator implements Iterator<Foo>{…} class BarIterator implements Iterator<Bar>{…} class MyCollection { public Iterator<Foo> foos { return new FooIterator(this); }; public Iterator<Foo> bars { return new BarIterator(this); }; } It would matter for Iterable. You cannot have this: class MyCollection implements Iterable<Foo>, Iterable<Bar>{…} , but there, there’s a second problem: in Java, you can’t have overloads that only differ in their return type.
- comex 2y agoIt's not useful to have a type implement Iterator for multiple element types. But in Rust, if Iterator took the element type as a generic parameter, it would be possible. The fact that it's not useful is precisely why Rust makes the element type an associated type instead. The benefits of using an associated type (compared to using a generic parameter in Rust, not compared to Java) include clarity, possibly improving type inference, and avoiding issues with crate coherence by requiring that the Iterator impl live in the crate that defines the input type, rather than the one that defines the output type.
- bombela 2y agoAssociated type: pub trait Iterator { type Item; [...] } vs generic type: pub trait Iterator<Item> {} The associated type version can only be implemented once per MyType. impl Iterator for MyType { type Item = i32; } The generic version can be implemented multiple times. For example: impl Iterator<String> for MyType impl Iterator<i32> for MyType https://doc.rust-lang.org/book/ch19-03-advanced-traits.html https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
- tsimionescu 2y agoI understand that part. But the comparison with Java is still bizarre, or perhaps just wrong. Java has nothing similar to this: impl Iterator for MyType { type Item = i32; } If you defined Iterator like this: interface Iterator { interface Item{} public Item next(); ... } Then Iterator.Item is the same type for all implementations, each implementation doesn't get to define its own type. Furthermore, a Java type can't implement both Iterator<Integer> and Iterator<String>: it has to implement a single one. So Java's Iterator<T> is in almost all ways equivalent to Rust's Iter, with its one associated type, and not to a Rust BadIter<T>. And this has nothing to do with how methods are dispatched, it has everything to do with the semantics of their respective type systems.
- tadfisher 2y agoJava has a pattern that is a superset of this, which is the self-recursive generic type: interface Foo<T extends Foo<T>> class Bar implements Foo<Bar> This is used to implement associated-type and Self-type behavior, at the expense of boilerplate. You can have multiple levels of associated types, though, which is murderous on the pinkies (or whatever finger types the angle bracket keys in your layout).