Division operation using Generics and Durations

Hey folks! Jumping to Rust from a predominately C++ and Python background. I'm diving in by creating my own DSP library (starting with a basic PID controller). In C++, I would build libraries like this using templates for the obvious benefit of being able to define my type on an as needed basis. When it comes to operations such as multiplying or dividing by a discrete time step, the auto keyword was very helpful for working around this ambiguous type definition.

In Rust, however, I'm a little stumped. I have defined a Number trait (duplicated from an example) as follows:

pub trait Number: PartialOrd + num_traits::Signed + Copy {}
impl<T: PartialOrd + num_traits::Signed + Copy> Number for T {}

With the Generic I was able to define my PID struct and impl appropriately (I believe):

struct PIDController<T: Number> {
    pub setpoint: T,
    pub output_limits: Limits<T>,
    pub kp: T,
    pub ki: T,
    pub kd: T,
    pub p_limits: Limits<T>,
    pub i_limits: Limits<T>,
    pub d_limits: Limits<T>,
    band_limit_i: T,
    prev_time: SystemTime,
    prev_error: T,
    first_sample: bool,
    ei: T,
impl<T> PIDController<T>
where T: Number,
T: From<f32> {
    // Create+Initialize new PIDController
    pub fn new() -> Self {
        Self {
            setpoint: T::zero(),
            output_limits: Limits {
                lower_limit: T::zero(),
                upper_limit: T::zero(),
            kp: T::zero(),
            ki: T::zero(),
            kd: T::zero(),
            p_limits: Limits {
                lower_limit: (T::zero()),
                upper_limit: (T::zero()),
            i_limits: Limits {
                lower_limit: (T::zero()),
                upper_limit: (T::zero()),
            d_limits: Limits {
                lower_limit: (T::zero()),
                upper_limit: (T::zero()),
            band_limit_i: T::zero(),
            prev_time: SystemTime::now(),
            prev_error: T::zero(),
            first_sample: true,
            ei: T::zero(),

The issue I'm running into is in my update function I will want to divide or multiply my Generic by a Duration, which has the ability to return as various "number" types (u32, u64, f32, etc...).

// Division
let mut ed = (error - self.prev_error) / T::from(dt.as_secs_f32());
// Multiplication
self.ei.clone() + error * T::from(dt.as_secs_f32()) * self.ki

How can I implement a multiplication or division operation (or any mathematical operation) where I want to evaluate a Generic with a value of known type? I ca

You can see in the example I added the From trait(?) for the f32 type but that obviously breaks if I use any type other than f32 or f64. I assume there's a better way than adding a From variation for every type I want to support:

T: From<u8>,
T: From<u16>,
T: From<u32>,
T: From<f32>,
etc ...

I tried doing my due-diligence to find the answer on my own but am maybe missing a key piece of terminology to find what I'm looking for. Any help/feedback is appreciated. Thanks in advance!

I'm not sure if num::FromPrimitive is what you're looking for? Its conversion methods are fallible though, i.e. they can return None, which must be handled.

There is also num::NumCast to cast to Option<Self> from a generic type T: ToPrimitive.

use num::{cast, Num, NumCast};

fn generic_mul_2<T>(x: T) -> T
    T: Num + NumCast,
    x * cast(2).unwrap()

fn main() {
    println!("{}", generic_mul_2(2.2f64));


1 Like

The num crate with Num + NumCast seems to be exactly what I was looking for. Thank you for the suggestion! The implementation and computations look like this now ->

impl<T> PIDController<T>
    T: Number,
    T: Num + NumCast,
let ep = error * self.kp;
// D
let mut ed = (error - self.prev_error) / cast(dt.as_secs()).unwrap();
ed = num_traits::clamp(
    ed * self.kd,

if error.abs() < self.band_limit_i {
    self.ei = num_traits::clamp(
        self.ei + error * cast(dt.as_secs()).unwrap() * self.ki,
    } else {
        self.ei = T::zero();

    result = ep + ed + self.ei;

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.