genericsrust

Iterate over a generic iterable without having to clone


I am trying create a struct that stores a copy of an iterable of iterables of u32 and the total of the maximums of the inner iterable. I want to iterate over the passed in data without wasting the memory to clone the input iterable.

I have the code below, but I can only get it to work if I add .clone() between data and .into_iter().

What do I need to do to get this working?

I'm open to the struct instead storing a reference to the iterable as it's intended to be a sorting tag that is displayed after sorting.

use std::collections::{BTreeSet, LinkedList};

struct Totalled<T>
where
    T: IntoIterator + Clone,
    T::Item: IntoIterator<Item = u32>,
{
    data: T,
    total: u32,
}

impl<T> Totalled<T>
where
    T: IntoIterator + Clone,
    T::Item: IntoIterator<Item = u32>,
{
    fn new(data: &T) -> Self {
        let total = data
            .into_iter()
            .map(|inner| inner.into_iter().max().unwrap_or(0))
            .sum();
        Totalled {
            data: data.clone(),
            total,
        }
    }
}

fn main() {
    let mut list = LinkedList::new();
    list.push_back(BTreeSet::from([1, 2, 3]));

    let totalled = Totalled::new(&list);
    println!("Total: {}", totalled.total);
    for set in totalled.data {
        println!("{:?}", set);
    }
}

Solution

  • Fixes:

    You'll notice that I didn't change main(). Totalled is used exactly the way you wrote it; I just changed how it's implemented.

    struct Totalled<T> {
        data: T,
        total: u32,
    }
    
    impl<'a, T> Totalled<T>
    where
        T: IntoIterator + Copy,
        T::Item: IntoIterator<Item = &'a u32>,
    {
        fn new(data: T) -> Self {
            let total = data
                .into_iter()
                .map(|inner| inner.into_iter().max().unwrap_or(&0))
                .sum();
            Totalled { data, total }
        }
    }
    

    Playground

    If you aren't happy with the generics I'd say ditch them. You could bake the LinkedList and BTreeSet types into Totalled. One of the habits I had to unlearn when I came to Rust was trying too hard to be collection type agnostic. You can end up too generic. I'd probably write concrete types myself:

    struct Totalled<'data> {
        data: &'data LinkedList<BTreeSet<u32>>,
        total: u32,
    }
    
    impl<'data> Totalled<'data> {
        fn new(data: &'data LinkedList<BTreeSet<u32>>) -> Self {
            let total = data
                .into_iter()
                .map(|inner| inner.into_iter().max().unwrap_or(&0))
                .sum();
            Totalled { data, total }
        }
    }
    

    Playground