Rust is very aggressive towards beginners

In my adventures with Rust I came across a case in which it would be interesting to use a more rustonic way of approaching the problem. But before we go to the case, I have a struct that contains a Vec that can be read by the user, but I don't want to expose this Vec in any way. My approach was to learn about iterators and make practical use of it. Cool, that would be very interesting, I went to read the documentation and ...

Iterator nightmare

How many methods, how many lines, wait a moment, I just need to implement next and Item, very simple. I defined Item and now I will define next ... Hm this example is not useful, internally I use a Vec and not an i32. I research Google a little more and they are saying that I don't need to use Iterator but IntoIterator, hm that doesn't make sense but let's test it. It worked well but my Vec is being consumed. I research how to create a version that does not consume the structure and more difficulties arise and I end up not understanding anything.

In C ++ it would be more practical to do this, even with potential future problems it would still be more advantageous than researching so much and not solving the problem and ending up frustrated.

I believe there is a lack of a "Guide for idiots for advanced topics in Rust", I understand that almost everything in Rust has a purpose, although that purpose is only clear to anyone who is already a guru.

I don't want to speak ill of the Rust language and the community, just let my outburst, which must be the same as that of many others who chose to leave the language rather than persist.

[Automatic translation]

8 Likes

I agree that Rust has a documentation gap between beginner and expert resources. In part, the language simply hasn't been mature and widely-used for very long compared to C++ or Java, so equivalents to books like "Effective C++" have not yet been written for Rust. The Programming Rust book is perhaps the closest thing, especially for programmers with C or C++ experience. (It has multiple chapters on iterators!)

As for your specific problem, that page does start off by providing a link that is less cluttered by reference material and more focused on concepts:

This is the main iterator trait. For more about the concept of iterators generally, please see the module-level documentation.

Lastly, for anyone looking for simple examples of creating and returning an iterator over a collection of items, here's how you can do it by creating a custom Iterator type and implementing the next method:

struct MyStruct { items: Vec<String> }

impl MyStruct {
    fn strings(&self) -> MyIterator<'_> {
        MyIterator { index: 0, items: &self.items }
    }
}

struct MyIterator<'a> {
    index: usize,
    items: &'a [String],
}

impl<'a> Iterator for MyIterator<'a> {
    type Item = &'a String;
    
    fn next(&mut self) -> Option<&'a String> {
        let item = self.items.get(self.index);
        self.index += 1;
        item
    }
}

and here's how you can use an impl Trait return type to return an existing iterator type without exposing the name of the type:

struct MyStruct {
    items: Vec<String>
}

impl MyStruct {
    fn strings(&self) -> impl Iterator<Item = &String> {
        self.items.iter()
    }
}
30 Likes

Incredible, this kind of really practical and simple example that should be in the documentation. Thanks.

4 Likes

By the way, you might be surprised at how hard it is for brand-new beginners to find understandable C++ resources, too. For example, the iterator documentation at cppreference looks like this: Iterator library - cppreference.com

Now, imagine you have never implemented a C++ iterator before, and you are staring at that page. What do you do? :slight_smile:

25 Likes

I would abandon C ++ and be happy with "for" and "while" in the C language :sweat_smile:

11 Likes

My take is that Rust has obviously been created by some "big brain" people and the early adopters and those writing the documentation that is available are similarly big brain. Naturally they have not targeted material at raw beginners who flounder around not understanding the language or the language used to describe the language. If you see what I mean.

That is no criticism of anyone. The docs we have are great, starting from The Book. It's just that we have not reached the days of "Rust for Dummies" yet.

As for the question in the OP, it makes me wonder. In my year and a half of Rust I have never felt the need to write an iterator for anything. I have just been using the available containers wan whatever comes with them.

The real question in the OP seems to be about "I have a struct that contains a Vec that can be read by the user, but I don't want to expose this Vec in any way."

It's not clear to me why that requires creation of an iterator, Vec already has that: Processing a Series of Items with Iterators - The Rust Programming Language

1 Like

But the purpose is clear, I wanted to learn about iterators and make practical use of them, for no reason. Is it wrong to want to know more about Rust? I come from C++ and exposing class fields there is a somewhat controversial subject for several reasons, such as creating possible dangerous references and API inconsistencies.

2 Likes

On the contrary. It's a very admirable goal. Likely the reason we are all here.

I was just somewhat confused as to whether the main point of your OP was data hiding or iterators.

But heck, why not both!

I do get the point about public fields in C++.

Ryan Levik has a great presentation on writing iterators here: Rust Stream: Iterators - YouTube

3 Likes

@rafaelcout Here is something I put together with input from many after having had a similar experience with the section on iterators. I’m up for feedback.

4 Likes

I just read your pdf and it was good for you to clarify the three distinctions of purpose of the iterators, move, read and mut, I didn't even see it in the Rust Book or elsewhere.

IntoIterator
"By implementing IntoIterator for a type, you define how it will be converted to an iterator. This is common for types which describe a collection of some kind.

One benefit of implementing IntoIterator is that your type will work with Rust's for loop syntax."

This description of IntoIterator needs to be improved, it seems that its main function is to be a syntactic sugar. It should be stated that it is to produce iterators to move, read or mutate the items in the collection.

If you hadn't commented, I would have gone to study IntoIterator and stuck with it for days again. I will go deeper and I will probably have doubts later.
Thanks.

1 Like

One thing that helps a while lot in reading the docs (and isn't obvious) is to notice what kind of self is used in methods. For IntoIterator

pub fn into_iter(self) -> Self

tells us that it consumes the object upon being called. So it's only useful if you don't want to keep your data after iterating.

This statement can be misleading. Any Iterator can be used with the for loop syntax. IntoIterator is only needed for a type that is not an Iterator to be used directly in the for loop syntax.

6 Likes

I'm not sure what you are getting at. The fact that Iterators work with for is only because they are also IntoIterator due to a blanket impl in std. For loops are desugared into a call to IntoIterator::into_iter() to begin with, so there's nothing misleading in there. Furthermore, @EdmundsEcho even quotes one of my posts in his PDF which states that the primary purpose of IntoIterator is to decouple iteration from containers so that containers don't need to store iteration-related extra state. There's nothing misleading in that statement, either.

4 Likes

I just meant that it can be misleading to someone who doesn't know about the blanket implementation. The thing you need to do to work with for loops is to create an Iterator, not an IntoIterator. The latter does require you to implement an Iterator, but to a newbie that's just extra confusion.

1 Like

No, that's exactly the point: this assertion of yours is plainly and factually incorrect. for loops work with IntoIterator, therefore they also happen to work with Iterator. But you don't need an Iterator. For example, you can write for elem in vec![1, 2, 3] even though Vec<i32> is not an Iterator.

4 Likes

No, you cannot create a FromIterator (edit: I meant IntoIterator) without also creating an Iterator. That is what a FromIterator (edit: I meant IntoIterator) does, so it is factually correct that if I create a type that I want to be able to iterate over, I need to create an Iterator. I do not need to know that there is such a thing as a FromIterator. I agree that there is value in understanding FromIterator, but I think that for a novice, there is even more value in being directed at the easy thing to do.

1 Like

You are confusing FromIterator with IntoIterator and the direction of their dependency on Iterator. Again, for the last time, for loops work with IntoIterator, and you can definitely create an IntoIterator without also needing an Iterator.

Indeed, for FromIterator, you need an Iterator, but that has nothing to do with the desugaring of for loops. FromIterator is a completely different and unrelated helper trait that makes Iterator::collect work.

So, FromIterator is the complementer of IntoIterator in a sense: while IntoIterator turns a collection into an iterator (and thus doesn't need an iterator to begin with), FromIterator instead turns an iterator into a collection (so it does need an iterator which it subsequently consumes).

1 Like

I think I see where the misunderstanding might be... hopefully without misunderstanding*:

Using:

... is true because Vec does not implement Iterator, IntoIter does. The "two concerns" are separate:

However, taking a step back, i.e., without knowing about IntoIter, when I was first thinking about the goal of iteration, my naive, if not colloquial way to state the task went something like:

how can I implement Iterator for MyCollection<T>?

... when, without knowing it at the time, what I really meant was:

how can I introduce a means to iterate over MyCollection<T>?

The first is unfortunately more than imprecise, but wrong. And I suspect is one of the points being made: the solution does not involve implementing Iterator for MyCollection<T>.

On a separate note, with a better understanding of how the separation of concerns occurs in Rust, it opens up another opportunity that avoids the need to implement Iterator myself:

MyCollection<T> -> Type that *already* implements Iterator

In a way, this is what Vec<T> and Array do when T is a borrow type (& and &mut); they each use Iterators from slice. I said "in a way", because those implementations of Iterator is less a choice, but a must by definition of the data structure.

Where the "in a way" angle is useful: I can't think of a MyCollection<T> where I did not leverage the Rust std::collections. This opens up lots of ways to avoid implementing Iterator or even IntoIterator for that matter (MyCollection<T> -> something in std::collections).

Re timing of when to explain the details, in my experience I got the concept (iteration) well before coming to Rust :)) - as most. However, the presentation in the book obfuscated both what I understood to be true and quickly became a source of confusion when trying to figure out the most expeditious way to “plug” my collection into the Rust infrastructure. In my view, the Rust iteration machinery/design is really cool and something worth getting to know “nearly” from the get go. As I’ve said before, if a fold is the universal constructor, iteration is core (fold ~ IntoIterator + FromIterator).... so worth it in my view.

*Note: the post is a meaningfully update to the original post that had a critical "miss" on my part: big thank you to @SkiFire13 for pointing it out, and @H2CO3 who's statement I miss-interpreted.

2 Likes

That's not true, that piece of code will use std::vec::IntoIter which is not an iterator implemented by slices.

7 Likes

Per usual, you are correct. I updated the post to ensure the point could be made.

This all said, it adds another interpretation to what @H2CO3 was trying to say about Vec<i32>

1 Like

I'm not sure I follow this exactly. You said you can use for loops with something that implements IntoIterator without also needing an Iterator, but the IntoIterator trait has to give you something back and and that thing must implement Iterator, does it not?

The struct std::vec::IntoIter implements Iterator.

1 Like