#Arithmetic with integral generic types

17 messages · Page 1 of 1 (latest)

distant anvil
#
use num_traits::{PrimInt,NumCast,NumOps};
pub struct GameState<T> 
    where T: PrimInt
{
    guess_bounds: GuessBounds<T>,
    guess_count: T,
    to_guess: T
}
impl<T> GameState<T>
    where T: PrimInt
{
    pub fn new(guess_bounds: GuessBounds<T>, to_guess: T) -> Self {
        GameState { guess_bounds, guess_count:NumCast::from(0).unwrap(), to_guess } //guess_bounds is short for guess_bounds:guess_bounds
    }

    pub fn process_guess(&mut self, guess: &Guess<T>) -> super::guess::GuessState { //() is unit type aka void (implicit when -> is left out)
        self.guess_count = NumOps::add(NumCast::from(self.guess_count).unwrap(), NumCast::from(1).unwrap());
    }
}
  self.guess_count = NumOps::add(NumCast::from(self.guess_count).unwrap(), NumCast::from(1).unwrap());
   |                                        ^^^^^^^^^^^^^ the trait `NumCast` is not implemented for `dyn NumOps<_, _, Output = _, Output = _, Output = _, Output = _, Output = _>`
   |
   = help: the following other types implement trait `NumCast`:
             Wrapping<T>
             f32
             f64
             i128
             i16
             i32
             i64
             i8
           and 7 others

I've tried many things to simply add a 1 to self.guess_count but cannot figure it out. I am pretty new to generics and traits in general but I'd like to figure this out as it is a good learning opportunity.

gleaming night
#

PrimInt requires the type to also implement Add, Zero, and One - so in your case you can get a "zero" T with T::zero(), and a "one" T with T::one()

You can also just use the regular addition too

#

?play

use num_traits::{PrimInt,NumCast,NumOps};
use std::marker::PhantomData;

pub struct GuessBounds<T>(PhantomData<T>);
pub struct Guess<T>(PhantomData<T>);
pub struct GuessState;

pub struct GameState<T> 
    where T: PrimInt
{
    guess_bounds: GuessBounds<T>,
    guess_count: T,
    to_guess: T
}
impl<T> GameState<T>
    where T: PrimInt
{
    pub fn new(guess_bounds: GuessBounds<T>, to_guess: T) -> Self {
        GameState { guess_bounds, guess_count: T::zero(), to_guess } //guess_bounds is short for guess_bounds:guess_bounds
    }

    pub fn process_guess(&mut self, guess: &Guess<T>) -> GuessState { //() is unit type aka void (implicit when -> is left out)
        self.guess_count = self.guess_count + T::one();
        GuessState
    }
}
cosmic cipherBOT
distant anvil
#

Ah, cool that does work. Why doesn't self.guess_count += T::one(); work though?

gleaming night
#

Because += requires the AddAssign trait to be implemented which PrimInt (nor the supertraits) doesn't guarantee - you could do it if you added AddAssign as an additional constraint on "T" though

PrimInt requires Num which requires NumOps which requires Add though, so the regular addition works fine on T

#

?play

use num_traits::{PrimInt,NumCast,NumOps};
use std::marker::PhantomData;
use std::ops::AddAssign;

pub struct GuessBounds<T>(PhantomData<T>);
pub struct Guess<T>(PhantomData<T>);
pub struct GuessState;

pub struct GameState<T> 
    where T: PrimInt
{
    guess_bounds: GuessBounds<T>,
    guess_count: T,
    to_guess: T
}
impl<T> GameState<T>
    where T: PrimInt + AddAssign
{
    pub fn new(guess_bounds: GuessBounds<T>, to_guess: T) -> Self {
        GameState { guess_bounds, guess_count: T::zero(), to_guess } //guess_bounds is short for guess_bounds:guess_bounds
    }

    pub fn process_guess(&mut self, guess: &Guess<T>) -> GuessState { //() is unit type aka void (implicit when -> is left out)
        self.guess_count += T::one();
        GuessState
    }
}
cosmic cipherBOT
gleaming night
#

That's the same with the additional AddAssign bound

distant anvil
#

Wait so do all primitive integral types implement AddAssign trait?

gleaming night
distant anvil
#

Ah very interesting, that makes way more sense. I had no idea that all those operators and stuff were implemented as traits. So basically, even though all primitive integral types implement AddAssign trait, compiler only knows you want a PrimInt which does not implement AddAssign trait. So basically compiler doesn't actually look at underlying type to see what traits it has it looks at your trait constraint and checks what traits it implements?

gleaming night
#

Yeah exactly, without any bounds on your generic types you're defining your GameState to support any type T - but that also means the compiler knows nothing about the capabilities of T in that case, there's nothing stopping you from passing a string, or a custom type

Then by adding trait bounds you're limiting which types can actually be used, but also providing knowledge of what those types are capable of in code

So the compiler only allows you to do the things defined by the traits you add to your trait bounds (or supertraits of those)

distant anvil
#

Excellent, thank you! One last question, how would I write self.guess_count = self.guess_count + T::one(); if I wanted to add say 250 to it?

gleaming night
#

Ah yeah, well there you can just use the NumCast::from that you were already using, since that's also guaranteed by T being a PrimInt

#

?play

use num_traits::{PrimInt,NumCast,NumOps};
use std::marker::PhantomData;
use std::ops::AddAssign;

pub struct GuessBounds<T>(PhantomData<T>);
pub struct Guess<T>(PhantomData<T>);
pub struct GuessState;

pub struct GameState<T> 
    where T: PrimInt
{
    guess_bounds: GuessBounds<T>,
    guess_count: T,
    to_guess: T
}
impl<T> GameState<T>
    where T: PrimInt
{
    pub fn new(guess_bounds: GuessBounds<T>, to_guess: T) -> Self {
        GameState { guess_bounds, guess_count: T::zero(), to_guess } //guess_bounds is short for guess_bounds:guess_bounds
    }

    pub fn process_guess(&mut self, guess: &Guess<T>) -> GuessState { //() is unit type aka void (implicit when -> is left out)
        self.guess_count = self.guess_count + T::from(250).unwrap();
        GuessState
    }
}
cosmic cipherBOT