Difficulty creating numeric trait

I ultimately want to bound the type parameters for another piece of code to only accept unsigned integers, but to allow different kinds of unsigned integers.

I have the unsigned integer marker trait definition and a minimal reproduction of the issue:

trait UnsignedInt: std::ops::Add<Output=Self> + Sized {}

impl UnsignedInt for u8 {}

But this doesn't work:

fn foo<I: UnsignedInt>(i: I) -> I {
    i + 1

failing with "expected type parameter, found integer".

I can't even get it to work if I try to explicitly state that 1 is of the same type as i:

let j: I = 1;

I've tried various things and this is confusing because:

  • if I implement a trait for a type, all values of that type should plug into functions which take any type implementing that trait
  • I have added a trait bound to the trait itself, stating that for any two values of a type implementing the trait, those values can be added to return another value of the same type

How does these facts taken together not imply what I'm trying to do is possible?

There's nothing in the code that tells the compiler that you can make an instance of the implementing type by means of assignment from an integer literal. I don't think this is possible to express in current Rust, but you could still add a From<u8> bound (for example) instead, either as a supertrait or to the function itself, then write i + I::from(1) to perform the conversion via a normal function call.

1 Like

But what would I implement From on?

The type should remain the same, while the trait should be recognized.

This does seem to work if I pass in both values to foo with the same type bounds:

foo<I: UnsignedInt>(i: I, j: I) -> I {
    i + j

It seems like no matter where I put the integer literal in the function body, it just kicks the can down the road and I get the same error. I don't actually need integer literals in expressions necessarily, but I still found this confusing.

I guess it makes sense to handle type inference between literals and generics very carefully.

There's nothing special about a literal in this context. The compiler doesn't allow you to assign the integer literal 1 to a value of type I, because there's no guarantee that an arbitrary type I is an integer. Rust generics work in a way that you have to specify every property of a type that you want to rely on upfront, via trait bounds and other constraints.

After specifying From<u8>, you wouldn't (necessarily) implement From<u8> yourself – the only change it would impose is that your trait would now only work with types that implement From<u8> themselves. I assumed this was OK since you seem to have wanted to impl UnsignedInt for u8.

Here's a playground demonstrating my suggestion.


Adding the From bound to the marker trait itself wouldn't work just because I want to implement this for other unsigned integer types too. I only included one in the example for brevity.

You did lead me to understanding the problem, though.

My bad I just saw your playground link and From works.

Nice. I assumed it would limit the impl to just u8. But it limits it to anything that can come from u8. Sort of a network of types there. Neat.

Again, thank you

Going further, specifically what unsigned type that you want to use doesn't implement From<u8>? I'm pretty sure all unsigned integer types should implement this trait, because u8 is the smallest unsigned integer type in Rust. So:

  • If you want to use other unsigned primitives, they work out of the box, the playground above demonstrates that.
  • If you have your own unsigned integer type, e.g. an unsigned bigint, then you can implement the conversion yourself.
  • If you rely on a 3rd-party type and it doesn't already implement From<u8>, you should file an issue and ask the author for it. (I mean this!)
1 Like

From is a generic conversion trait for all sorts of infallible conversions. u8 is not the only type that can be made from a u8.

Creating a conversion trait just to assert type equality would have been confusing and unnecessarily complicated; likewise it would have been useless on my part to provide an answer that pretends to be generic whereas it only works with a single concrete type.

1 Like