0

I have some problems with a generic implementation of a method:

use std::collections::BTreeMap;
use global::entity::EntityId;

struct simple_system<T> {
    list_sum: BTreeMap<EntityId, T>,
    list_com: BTreeMap<EntityId, Vec<T>>,
}

impl<T> simple_system<T> {
    pub fn new() -> simple_system<T> {
        simple_system {
            list_sum: BTreeMap::new(),
            list_com: BTreeMap::new(),
        }
    }

    pub fn addComponent(&mut self, id: EntityId, comp: T) {
        self.list_com.entry(id).or_insert_with(Vec::new).push(comp);
        match self.list_sum.get_mut(&id) {
            Some(v) => *v = *v + *comp,
            None => self.list_sum.insert(id, comp),
        }
    }
}

with the following errors.

error[E0614]: type `T` cannot be dereferenced
  --> src/main.rs:20:34
   |
20 |             Some(v) => *v = *v + *comp,
   |                                  ^^^^^

error[E0369]: binary operation `+` cannot be applied to type `T`
  --> src/main.rs:20:29
   |
20 |             Some(v) => *v = *v + *comp,
   |                             ^^^^^^^^^^
   |
   = note: an implementation of `std::ops::Add` might be missing for `T`

I don't know what I have to change to get it to work. I use it with u32 type so it should have an + operator.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    Please provide a minimal working example (apart from your actual problem), e.g. include some stub `EntityId`. – Stefan Nov 17 '17 at 12:52
  • 1
    Related: https://stackoverflow.com/questions/29184358/requiring-implementation-of-mul-in-generic-function (but not a duplicate imho: the OP in the linked question already knew to use traits, just now how) – Stefan Nov 17 '17 at 12:58
  • Probably a duplicate of https://stackoverflow.com/questions/47115337/function-generics-an-implementation-of-stdopsadd-might-be-missing-for-t (which shouldn't have been closed as duplicate) – Stefan Nov 17 '17 at 12:58
  • The Rust standard naming is `PascalCase` for types and `snake_case` for function and variable names; please adopt it. – Shepmaster Nov 17 '17 at 14:20
  • @Stefan *which shouldn't have been closed as duplicate* — see how it says "marked as duplicate by [...] Community"? That means that [the OP voted to close it as a duplicate](https://meta.stackoverflow.com/q/287763/155423). – Shepmaster Nov 17 '17 at 14:22
  • You should **always** write the non-generic version first. Only when that works should you make it generic. For example, a `u32` **cannot be dereferenced**, so I don't even know what your code would be doing. – Shepmaster Nov 17 '17 at 14:45
  • I will try this. Maybe it enlightens me a little bit. :) Thanks for your help. – Mike Kanzler Nov 17 '17 at 15:07

2 Answers2

5

The Rust generics systems doesn't work the way C++ templates do: in C++ the compiler doesn't check whether the code actually compiles with any type in advance.

Rust makes sure the function compiles with any type that fulfills the listed requirements (called trait bounds). The compiler already told you what is missing: std::ops::Add might be missing for T, so ask for it:

impl<T: Add<Output = T>> simple_system<T> { /* … */ }

This will not fix everything; your code has other issues as well.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Stefan
  • 5,304
  • 2
  • 25
  • 44
1

And here the Solution:

  1. At first you should write a working non generic (c++ template) version and then evolve it to a generic version.

    use std::collections::BTreeMap;
    
    #[derive(Debug)]
    struct SumUpSystem {
        list_sum: BTreeMap<u64, i32 >,
        list_com: BTreeMap<u64, Vec<i32> >
    }
    
    impl SumUpSystem {
        pub fn new() -> SumUpSystem {
            SumUpSystem {
                list_sum: BTreeMap::new(),
                list_com: BTreeMap::new()
            }
        }
    
        pub fn add_component(&mut self, id: u64, comp: i32) {
            self.list_com.entry(id).or_insert_with(Vec::new).push(comp);
    
            let mut insert = false;
            match self.list_sum.get_mut(&id) {
                Some(x) => *x = *x + comp,
                None => insert = true
            }
    
            if (insert) {
                self.list_sum.insert(id, comp);
            }
        }
    
        pub fn sum(& self, id: u64) -> i32 {
            if let Some(x) = self.list_sum.get(&id) {
                *x
            } else {
                panic!("Invalid id: Not in system!");
            }
        }
    }
    
    #[cfg(test)]
    mod test {
        use super::*;
    
        macro_rules! init_test {
            ($v:ident) => (let mut $v : SumUpSystem = SumUpSystem::new(););
        }
    
        #[test]
        fn add_component() {
            init_test!(s);
            s.add_component(1, 13);
            assert_eq!(s.sum(1), 13);
            s.add_component(1, 26);
            assert_eq!(s.sum(1), 13 + 26);
        }
    }
    
  2. The generic (c++ template). You should read the Trait section of the Rust Documentation to understand how/why it works.

    use std::collections::BTreeMap;
    use std::ops::Add;
    
    #[derive(Debug)]
    struct SumUpSystem<T> {
        list_sum: BTreeMap<u64, T >,
        list_com: BTreeMap<u64, Vec<T> >
    }
    
    impl <T: Add<Output=T> + Clone> SumUpSystem<T> {
        pub fn new() -> SumUpSystem<T> {
            SumUpSystem {
                list_sum: BTreeMap::new(),
                list_com: BTreeMap::new()
            }
        }
    
        pub fn add_component(&mut self, id: u64, comp: &T) {
            self.list_com.entry(id).or_insert_with(Vec::new).push(comp.clone());
    
            let mut insert = false;
            match self.list_sum.get_mut(&id) {
                Some(x) => *x = x.clone() + comp.clone(),
                None => insert = true
            }
    
            if insert {
                self.list_sum.insert(id, comp.clone());
            }
        }
    
        pub fn sum(& self, id: u64) -> T {
            if let Some(x) = self.list_sum.get(&id) {
                x.clone()
            } else {
                panic!("Invalid id: Not in system!");
            }
        }
    }
    
    #[cfg(test)]
    mod test {
        use super::*;
    
        macro_rules! init_test {
            ($v:ident) => (let mut $v : SumUpSystem<i32> = SumUpSystem::new(););
        }
    
        #[test]
        fn add_component() {
            init_test!(s);
            s.add_component(1, &13i32);
            assert_eq!(s.sum(1), 13i32);
            s.add_component(1, &26i32);
            assert_eq!(s.sum(1), 39i32);
        }
    }