1

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?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
nbari
  • 25,603
  • 10
  • 76
  • 131
  • It looks like your question might be answered by the answers of [How do I convert between numeric types safely and idiomatically?](https://stackoverflow.com/q/28273169/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster May 19 '20 at 19:00
  • *`as_nanos() = u128`* — there are no systems that Rust runs on where `usize` is big enough to hold a `u128`. What do you want to happen when someone passes in `u128::MAX`? – Shepmaster May 19 '20 at 19:01

2 Answers2

2

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.

Jmb
  • 18,893
  • 2
  • 28
  • 55
  • `...(number: T) where >::Error: Debug, >::Error: Debug`, is there a way to prevent the `where`? only asking for learning more about other possible alternatives – nbari May 20 '20 at 10:15
  • 1
    The where is required for the calls to `unwrap`. If you replace them with `unwrap_or_else (|_| unreachable!())` then [you can get rid of the `where` clauses](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3f0bc050fcc189c57c104340db9d76a8) – Jmb May 20 '20 at 11:30
  • [error: literal out of range for `i32`](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=09e5b09ff9da8d207de4729381b0c712) when formatting `4_294_967_295` something that works when using `as usize`, how could "all" ranges be accepted or behave like `as usize` – nbari May 21 '20 at 09:21
  • I tried using `#[allow(overflowing_literals)]` but still get the error – nbari May 21 '20 at 09:32
  • That's a different issue: for literals (ie. numbers that are typed directly in the code), the compiler attempts to guess which type to use and falls back to `i32` if it can't. Since the function is generic the compiler can't guess, but the number doesn't fit in an `i32`. So you have to be explicit and [write `4_294_967_295usize`](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=36583d836067aa13f4a42d13c0cb48d7). – Jmb May 22 '20 at 11:20
  • Is there a way "one size fits all" approach? or probably better to just continue using `as usize` to prevent extra points of failure. – nbari May 22 '20 at 15:10
  • 1
    There is no way to have a generic function that will accept `4_294_967_295` without the `usize` suffix. Whether it is preferable to have a function that's easy to call in most situations or to be explicit about the conversions is something you will have to decide depending on your precise use-case. – Jmb May 23 '20 at 17:33
1

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.

Optimistic Peach
  • 3,862
  • 2
  • 18
  • 29