How Do I Iterate over collection with temp var

I want to iterate over vec to find the best element. In c++ I will save reference to first element and will compare with the next element and if the next is better I will point the refference to next element and so on. How do I do this in rust

Here is link to rust playground with example. I want to remove the line with "// !!! I want to remove that line"
The problem is that when I unpack optional to read the data, the values are consumed and I need to reinitiliaze it again

And here is my original code :

fn find_intersection<'a>(&self, ray : &Ray, scene : &'a Scene) -> Option<(&'a Box<dyn SceneElement>, IntersectionInfo)> {
        let mut result : Option<(&Box<dyn SceneElement>, IntersectionInfo)> = None;
        for element  in &scene.elements {
            if let Some(info) = element.geometry().intersect(&ray) {
                let found_new = if let Some((node, old_info)) = result {
                    let t = info.distance < old_info.distance;
                    result = Some((node, old_info));
                    t
                } else {true};

                if found_new {
                    result = Some((element, info));
                }
            }
        }

        result
    }

You can match on &result to borrow the values temporarily (Rust Playground):

fn find_intersection<'a>(scene: &'a Scene) -> Option<(&'a Box<dyn Something>, B)> {
    let mut result: Option<(&Box<dyn Something>, B)> = None;
    for element in &scene.elements {
        if let Some(info) = element.intersect() {
            //                                           vvvvvvv
            let found_new = if let Some((_, old_info)) = &result {
                info.data < old_info.data
            } else {
                true
            };

            if found_new {
                result = Some((element, info));
            }
        }
    }

    result
}

This feature is known as match ergonomics. If you match (or if let) on a reference, you'll get the same kind of reference in the new variables.

1 Like

You could also spell the method like so:

fn find_intersection<'a>(&self, ray : &Ray, scene : &'a Scene) -> Option<(&'a Box<dyn SceneElement>, IntersectionInfo)> {
    scene
        .elements
        .iter()
        .filter_map(|element| {
            element.geometry().intersect(ray).map(|info| (element, info))
        })
        .min_by(|t1, t2| t1.1.distance.total_cmp(&t2.1.distance))
}
3 Likes

I was just about to post a very similar function. The ? operator also works very well for filter_map:

        .filter_map(|element| Some((element, element.geometry.intersect(&ray)?)))
1 Like

Thanks a lot for your answer. I'm still not convinced that iterators and combinators lead to better or at least same performance compared to carefully designed for cycle. So far it's look like wishful thinking. So far I don't have big enough application to test it. Do you have such experience ? What is your opinion on that topic ?

So far it's look like wishful thinking.

Despite being less direct / more abstract, the std iterators are pretty optimizied, and there are also combinators like chunks_exact that are aimed at auto-vectorization depending on your circumstance. So iterators can actually improve performance. That said, it's no guarantee; hand-tuned code or just incidentally non-iterator-using code can still be faster.


Therefore, my opinions are

  • either way might be faster, so
  • write it whichever way is most comfortable to you, then
  • only optimize when you know it's a relevant bottleneck, and also
  • don't assume it is a bottleneck or assume which approach is faster; measure instead.
6 Likes

What leads you to believe this? Do you have data to back it up? If not, then you should just write the code the more idiomatic, clearer way, and not worry about such (usually trivial) performance differences.

The lack of evidence for it, it's too good to be true (zero cost abstraction), the lack of explanation how it's achieved

Citation needed.

Several people have explained to you in this thread how iterators work. If you don't believe them, feel free to, but that is not going to be the fault of the language.

It happens due to inlining. All of the iterator functions involved take functions as generic arguments, and every single closure generates a new unique type. Because of how generics works, this means that the actual iterator functions you are calling are only ever called from your find_intersection method. This makes it pretty much guaranteed that the compiler will inline them.

Once they're inlined into the function, it isn't very difficult for the compiler to optimize it into something like a handwritten loop.

6 Likes