I'm trying to write a generic struct that will hold a collection and the sum of all the inserted values into that collection.
That is a generic type that keeps the sum of all added values to it.
pub struct SummedCollection<T>
where
T::Item: std::ops::Add<Output=T::Item> std::ops::Div<Output=T::Item>
{
sum: T::Item,
values: T,
}
impl<T> SummedCollection<T> {
pub fn new() -> Self{
SummedCollection {
sum: T::Item::default(),
values: T::new(),
}
}
pub fn push(&mut self, value: T::Item) {
self.values.push(value);
self.sum = self.sum value;
}
pub fn sum(&self) -> T::Item {
self.sum
}
}
The intended use would be:
let v: SummedCollection<Vec<i32>> = SummedCollection::new();
v.push(5);
v.push(10);
I would then expect: v.sum() == 15
.
I get an error "^^^^ associated type `Item` not found" on each assurance of T::Item, what do I need to do to access the nested generic (the i32 in my example)?
CodePudding user response:
The compiler doesn't know T::Item
. You intended T
to be some collection type, but didn't tell it to the compiler, so it doesn't know that.
You have to tell the compiler T
implements some collection trait. A trait like that doesn't exist in the standard library, but you can easily write your own:
pub trait Collection {
type Item;
fn push(&mut self, value: Self::Item);
}
// Example implementation, you can implement the trait for any collection type you wish
impl<T> Collection for Vec<T> {
type Item = T;
fn push(&mut self, value: T) {
self.push(value);
}
}
pub struct SummedCollection<T: Collection> {
sum: T::Item,
values: T,
}
impl<T> SummedCollection<T>
where
T: Collection Default,
T::Item: Default Copy std::ops::Add<Output = T::Item> std::ops::Div<Output = T::Item>,
{
pub fn new() -> Self {
SummedCollection {
sum: T::Item::default(),
values: T::default(),
}
}
pub fn add(&mut self, value: T::Item) {
self.values.push(value);
self.sum = self.sum value;
}
pub fn sum(&self) -> T::Item {
self.sum
}
}
Note I made some additional changes:
- Required
T::Item
to beCopy
andDefault
. It may be possible to work around this need, but it's easy this way. - Required
T: Default
and changedT::new()
toT::default()
, since we already have a default-constructible trait - no need to reinvent the wheel. - Moved some of the bounds from the struct to the impl, since it is better this way.
Edit: Thanks for @mcarton that pointed out that we can use Extend
for that:
#[derive(Debug)]
pub struct SummedCollection<Collection, T> {
sum: T,
values: Collection,
}
impl<Collection, T> SummedCollection<Collection, T>
where
Collection: Extend<T> Default,
T: Default Copy std::ops::Add<Output = T> std::ops::Div<Output = T>,
{
pub fn new() -> Self {
SummedCollection {
sum: T::default(),
values: Collection::default(),
}
}
pub fn add(&mut self, value: T) {
self.values.extend(std::iter::once(value));
self.sum = self.sum value;
}
pub fn sum(&self) -> T {
self.sum
}
}
But note that because it requires an additional generic paremeter it will affect users: instead of
let v: SummedCollection<Vec<i32>> = SummedCollection::new();
You'll have to write
let v: SummedCollection<Vec<i32>, _> = SummedCollection::new();
Or explicitly, of course:
let v: SummedCollection<Vec<i32>, i32> = SummedCollection::new();