Trait shorcut for a group of clauses

I want to create a shortcut for a group of 11 trait clauses in the nifti-rs project. Something like

/// Shortcut trait for reading image in generic context.
pub trait ImageReadable<T>
where
    T: DataElement,
    u8: AsPrimitive<T>,
    i8: AsPrimitive<T>,
    u16: AsPrimitive<T>,
    i16: AsPrimitive<T>,
    u32: AsPrimitive<T>,
    i32: AsPrimitive<T>,
    u64: AsPrimitive<T>,
    i64: AsPrimitive<T>,
    f32: AsPrimitive<T>,
    f64: AsPrimitive<T>,
{}

because this pattern is repeated 10 times in nifti-rs and several times in my personal project. My goal is that, with this "super trait", the where clause of the linked function would become as simple as

fn into_ndarray<T>(self) -> Result<Array<T, IxDyn>>
where
    T: ImageReadable<T>
{ ... }

However, when I try to use this shortcut, the compiler tells me that I need to add all clauses again. This is not super useful! Is this possible? What's the right syntax?

1 Like

This is a shortcoming of supertraits. They only include things in where clauses that are saying Self: some_bound; everything else is instead required to be repeated by the user of the trait. Let me see if I can find any approach that can help you here...

Edit: There doesn’t seem to be any straightforward way.

1 Like

Trait aliases exist on nightly under #![feature(trait_alias)]

This code will work for instance:

#![feature(trait_alias)]

trait Foo = std::fmt::Debug + std::fmt::Display;

fn foo<T: Foo>(val: T) {
    println!("{:?} {}", val, val)
}

fn main() {
    foo(23u32)
}

Playground link

2 Likes

Related issue: rust-lang/rust#20671

While @canndrew's code fails to show it, trait aliases work with non-Self bounds, so they should be able to solve your problem.

2 Likes

Thank you for the example and the link. Sadly, I'm not allowed to use nightly at my job. I wasn't sure about the syntax so I tested.

pub trait ImageReadable = where
    Self: DataElement,
    u8: AsPrimitive<Self>,
    i8: AsPrimitive<Self>,
    u16: AsPrimitive<Self>,
    i16: AsPrimitive<Self>,
    u32: AsPrimitive<Self>,
    i32: AsPrimitive<Self>,
    u64: AsPrimitive<Self>,
    i64: AsPrimitive<Self>,
    f32: AsPrimitive<Self>,
    f64: AsPrimitive<Self>;

fn into_ndarray<T>(self) -> Result<Array<T, IxDyn>>
where
    T: ImageReadable
{ ... }

This is what you had in mind, right?

For the record, I did find a non-straightforward way:

use num::cast::AsPrimitive;

trait ThisIs {
    type Actually: ?Sized;
}
impl<T: ?Sized> ThisIs for T {
    type Actually = T;
}

// A: Foo

// is supposed to replace

// A: Copy + 'static,
// u8: AsPrimitive<A>,
// i8: AsPrimitive<A>,

trait Foo: Copy + 'static {
    type U8: ThisIs<Actually = u8> + AsPrimitive<Self>;
    type I8: ThisIs<Actually = i8> + AsPrimitive<Self>;
}

impl<A> Foo for A
where
    A: Copy + 'static,
    u8: AsPrimitive<A>,
    i8: AsPrimitive<A>,
{
    type U8 = u8;
    type I8 = i8;
}

type SomeReturnValue<A> = Option<A>;
trait Bar {
    fn bar<A: Foo>() -> SomeReturnValue<A>;
}

// calling the bar method is no problem
#[allow(unused)]
fn i_can_call_something_with_foo_constraint<B: Bar>() {
    B::bar::<i32>();
}

struct MyStruct;

// implementing Bar is unfortunately nontrivial
impl Bar for MyStruct {
    fn bar<A: Foo>() -> SomeReturnValue<A> {
        // directly calling
        // needs_the_original_bounds::<A>()
        // here does not work.

        // The workaround to convince the type checker:

        fn bar_<A, U8, I8>() -> SomeReturnValue<A>
        where
            A: Copy + 'static,
            (): HelperTrait<<U8 as ThisIs>::Actually, <I8 as ThisIs>::Actually>,
            U8: AsPrimitive<A>,
            I8: AsPrimitive<A>,
        {
            <()>::call::<A>()
        }
        trait HelperTrait<U8, I8> {
            fn call<A>() -> SomeReturnValue<A>
            where
                A: Copy + 'static,
                U8: AsPrimitive<A>,
                I8: AsPrimitive<A>;
        }
        impl HelperTrait<u8, i8> for () {
            fn call<A>() -> SomeReturnValue<A>
            where
                A: Copy + 'static,
                u8: AsPrimitive<A>,
                i8: AsPrimitive<A>,
            {
                needs_the_original_bounds::<A>()
            }
        }
        bar_::<A, A::U8, A::I8>()
    }
}

fn needs_the_original_bounds<A>() -> SomeReturnValue<A>
where
    A: Copy + 'static,
    u8: AsPrimitive<A>,
    i8: AsPrimitive<A>,
{
    None
}

I also found a compiler bug along the way

4 Likes

Hi, I was interested in something very similar, coincidentally also image-related! For me, I wanted to add [T]: ... in the where clause of the supertrait. My problem can be minimized to that example Rust Playground

trait Foo {}
trait Bar: Clone where [Self]: Foo {}

// This function will not compile if I don't add the where clause
// I had hoped that it would be deduced from the Bar heritage.
fn main<B: Bar>(bar: B) { // where [B]: Foo {
    let bar_clone = bar.clone();
}

If I understand this correctly, what I'm hoping for is not possible.

PS: I hope I'm not deviating too much from the original post. I thought it was similar enough to not open a new one.

This RFC was accepted and you can see in the tracking issue that it is implemented in Chalk, which will eventually become rustc's trait solver.

I have no idea when that might be, and I doubt it will be implemented in pre-Chalk rustc.

2 Likes

@quinedot thanks a lot, that was super helpful. Chalk seems like it will solve so many things!