How to achieve `trait T: usize: From<T> { ... }`?

Hello,

I would like to achieve something like the following:

trait MyTrait:
  usize: From<Self>
{
  ...
}

... to effectively require that for implementations of trait MyTrait, there exists an impl of impl<T: MyTrait> From<T> for usize { ... }. The end-goal is to be able to use the trait in the following circumstance:

fn a(b: impl MyTrait) {
  let c = usize::from(b);
  ...
}

How can I introduce this bound, or implementation on the trait itself?

Thanks.

EDIT: I dislike (rightfully so, I think) the ergonomics of .into(), and prefer to explicitly use ::from(...) wherever possible.

You can place where clauses on trait definitions:

trait MyTrait: Sized
where
    usize: From<Self>,
{}

// errors with "the trait `From<()>` is not implemented for `usize`"
impl MyTrait for () {}

Note that you have to also bound on Sized, since that's implicitly required for any generic argument to the trait From.

3 Likes

Ah! I thought the trait T: ... was an alias for where clauses on trait declarations. Thank you so much!

See also https://users.rust-lang.org/t/different-ways-to-write-bounds-for-supertraits-associated-type/115127; you might not be able to escape having to explicitly add the where-bound at generic use sites.

2 Likes

It is, but only for Self: bounds. Like in other comtexts, where clauses are strictly more powerful than the various shorthand versions.

1 Like

On the plus side, those sites can just use where T: MyTrait rather than knowing it requires usize: From<Self>

This code fails to compile, saying "the trait bound usize: From<T> is not satisfied":

trait MyTrait: Sized
where
    usize: From<Self>,
{}

fn foo<T>()
where
    T: MyTrait,
{}
1 Like

Yeah, in my experience testing it, each callsite had to replicate the non-Self constraints.

Dang, you're right, I misunderstood that link completely! I knew I shouldn't post at 2 AM, but apparently I shouldn't post at 6 AM either. (Pretty much everything with AM is pretty suspect, honestly...)

That's 11 years old bug.

Means: yes, it's a bug and should be needed, but all… it existed for 11 years expected time to fix is 11 years, too. Lindy's law sounds comical when you first hear it, but works eerily good.

If you dare, you can use fun traits tech like this

trait Imply<T: ?Sized>: ImplyInner<T, Is = T> {}

trait ImplyInner<T: ?Sized> {
    type Is: ?Sized;
}

impl<_Self: ?Sized, T: ?Sized> Imply<T> for _Self {}

impl<_Self: ?Sized, T: ?Sized> ImplyInner<T> for _Self {
    type Is = T;
}

and then

trait MyTrait: Sized + Imply<usize, Is: From<Self>> {
    ...
}

(playground)

// demo impl
impl MyTrait for u16 {}

// demo use in function signature
fn f<T: MyTrait>(x: T, y: T) {
    let n: usize = x.into();
    let m = usize::from(y);
}
3 Likes

by doing this, the fllowing code does not compile either

but the following code can complie. do we need it? why nobody mentioned this.

fn a<T: MyTrait>(b: T)
where
    usize: From<T>,
{
    let u = usize::from(b);
}

Actually, robofinch did, right after mine:

It's a limitation of the type system as it currently stands.

I don't consider 20671 a bug as elaborating more bounds takes away choice from trait authors: the ability to loosen bounds without a breaking change.

Instead I consider it the lack of a feature: the ability to (ergonomically) opt into (or out of) implied bounds.

4 Likes

Why not simply

trait MyTrait: Into<usize> {}

fn a(b: impl MyTrait) {
  let c: usize = b.into();
  ...
}

Edit: Ah, I should've read the entire post.

I think this is what the edit/addition in the OP relates to:

though IMHO, this solution of “just use Into” is additionally very much specific to this case anyway, while the general question can stay relevant for all kinds of other settings :slight_smile:

3 Likes