nbari
nbari

Reputation: 27005

How to implement Into trait to convert all input to usize not using `as usize`?

I have a function that returns the compound duration based on an usize input:

pub fn format_dhms(seconds: usize) -> String 

If the input is 6000000:

println!("{}", format_dhms(6000000));

It returns:

69d10h40m

This works when the input is a number, but when I use the output of another function with a fixed type, I need to use as usize. For example, if I use the output of Duration using methods as_secs() = u64 or as_nanos() = u128.

When someone passes u128::MAX, I would like to deal with it like as usize does by truncating the input to the max accepted value.

This is what I am trying: (https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=4a8bfa152febee9abb52d8244a5092c5)

#![allow(unused)]
use std::time::Instant;

fn format<T: Into<usize>>(number: T) {
    if number == 0 {
        //println!("{}", number)
    } else {
        //println!("{}> 0", number)
    }
}

fn main() {
    let now = Instant::now();
    format(now.elapsed().as_nanos()); // u128
    format(now.elapsed().as_secs()); // u64
}

But some of the errors I get are:

error[E0277]: the trait bound `usize: std::convert::From<i32>` is not satisfied
    the trait `std::convert::From<i32>` is not implemented for `usize`
...
error[E0369]: binary operation `==` cannot be applied to type `T`

If I remove the <T: Into<size>> it works, but I need to use as usize.

 format(now.elapsed().as_nanos() as usize);

Is there a way I could convert the input to prevent using the as usize or how to achieve same behavior when input is just a number with no defined type?

Upvotes: 1

Views: 2023

Answers (2)

Jmb
Jmb

Reputation: 23453

You can use std::mem::size_of to check if the input type fits in a usize and use bit-manipulations to truncate when it doesn't:

use std::convert::{ TryFrom, TryInto };
use std::fmt::Debug;
use std::ops::BitAnd;
use std::time::Instant;

fn format<T: TryInto<usize> + TryFrom<usize> + BitAnd<Output=T>> (number: T)
    where <T as TryFrom<usize>>::Error: Debug,
          <T as TryInto<usize>>::Error: Debug
{
    let number: usize = if std::mem::size_of::<T>() <= std::mem::size_of::<usize>() {
        number.try_into().unwrap()
    } else {
        (number & usize::MAX.try_into().unwrap()).try_into().unwrap()
    };
    if number == 0 {
        //println!("{}", number)
    } else {
        //println!("{}> 0", number)
    }
}

Playground

Note that so long as you only use unsigned types, the unwraps should never fail since the check on type sizes ensures that the conversions are always valid.

Upvotes: 2

Optimistic Peach
Optimistic Peach

Reputation: 4318

Using the TryFrom trait, you can "try" to convert to a different type. Should the input number be too big for usize, you will get an error.

fn foo<T: TryInto<usize>>(x: T) -> usize {
    x.try_into().unwrap() // Will panic if
                          // x cannot fit
                          // into a usize.
}

Additionally, this does not have the same semantic effect as as casts. Since those will truncate, while this will just not work.


The real best practice in this case would be to just use regular trait bounds for numbers, instead of using usize, since some values don't fit in there:

fn format<
    T: Sub<Output = T> + 
       Mul<Output = T> + 
       Div<Output = T> + 
       Display + 
       PartialEq +
       From<bool>        //etc. for all the operations you need. 
    >(number: T) {
    if number == T::from(false) { // `false` turns into 0 for numbers. 
        //println!("{}", number)
    } else {
        //println!("{}> 0", number)
    }
}

The std number traits however, are rather barebones, so I'd recommend you look at num_traits.

Upvotes: 1

Related Questions