Reputation: 27005
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
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)
}
}
Note that so long as you only use unsigned types, the unwrap
s should never fail since the check on type sizes ensures that the conversions are always valid.
Upvotes: 2
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