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);
}
}
Fixes:
Change data: &T
to data: T
. Since T
is going to be a shared reference, change the bounds to T: IntoIterator + Copy
(references can be copied). You're passing in &list
, so copying data just copies that reference, it doesn't copy the whole list. Copying a reference is cheap.
Change the inner Item
type to &'a u32
. Iterating over a reference produces references to the items so we'll be seeing &u32
s rather than u32
s.
No need for any cloning. data
will be implicitly copied when data.into_iter()
is called, leaving it available for the Totalled { data, total }
line.
Remove the bounds from struct Totalled<T>
. It's best practice to omit bounds from struct declarations and just put them on the impl
blocks where they're required.
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 }
}
}
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 }
}
}