`Ext` type trait and concrete types

The TraitExt pattern is a common way to add lots of default functionality to a trait, but equally for this problem, one could consider the Iterator pattern, where extension methods are provided on the base trait, with default implementations.

It is possible to implement methods in a trait where we require the type parameters to be constrained. For example

trait MyTrait<T> {
    fn method(self) where T: Clone {
        // ...
    }
}

What I am looking for is the ability to do

trait MyTrait<T> {
    fn method(self) where T: bool {
        // ...
    }
}

, that is, to constrain a type all the way down to a concrete type. Is this possible?

No, this is not possible like that.

You could use where T: DummyTraitForBool and only provide impl DummyTraitForBool for bool {}, but Rust will not treat it as a boolean, but as an unknown type that has this or possibly other implementations.

1 Like

A shame.

Equality bounds only work for associated types, so you need an extra trait to make this work:

trait AssertType {
    type Is:?Sized;
    fn cast(self)->Self::Is where Self:Sized, Self::Is:Sized;
    fn cast_ref(&self)->&Self::Is;
    fn cast_mut(&mut self)->&mut Self::Is;
    
    fn cast_from(_:Self::Is)->Self where Self:Sized, Self::Is:Sized;
    fn cast_from_ref(_:&Self::Is)->&Self;
    fn cast_from_mut(_:&mut Self::Is)->&mut Self;
}

impl<T: ?Sized> AssertType for T {
    type Is = T;
    fn cast(self)->T where T:Sized { self }
    fn cast_ref(&self)->&T { self }
    fn cast_mut(&mut self)->&mut T { self }
    
    fn cast_from(x:T)->Self where T:Sized { x }
    fn cast_from_ref(x:&T)->&Self { x }
    fn cast_from_mut(x:&mut T)->&mut Self { x }
}

trait MyTrait<T> {
    fn method(&self) where T: AssertType<Is=bool> {
        /* ... */
    }
}

(Playground)

5 Likes

That is a lovely workaround! :stuck_out_tongue:

Why? What do you need this for? In the context of generics, requiring a type to be a specific concrete type is a code smell – it shows that you are not programming against the interface, but you are relying on implementation details. That's almost never what the proper solution is. If you tell us more about what your use case is, we could perhaps provide a solution that does not require concretizing the types.

1 Like

My use case is that given the following traits

pub trait Lens<T: ?Sized, U: ?Sized> {
    fn with<V, F: FnOnce(&U) -> V>(&self, data: &T, f: F) -> V;
    fn with_mut<V, F: FnOnce(&mut U) -> V>(&self, data: &mut T, f: F) -> V;
}

pub trait LensExt<T: ?Sized, U: ?Sized>: Lens<T, U> {
    // ..
}

add a method to LensExt that acts on lenses of type <T, bool> and returns lenses of type <T, bool> where the sign of the boolean has been flipped

    fn not(self) -> impl Lens<T, bool>
    where 
        Self: Lens<T, bool>
    {
        // ...
    }

EDITED

In the end I implemented it for U: Into<bool> and bool: Into<U>.

Full solution:

#[derive(Debug, Copy, Clone)]
pub struct Not;

impl<T> Lens<T, bool> for Not
where
    T: Into<bool> + Copy,
    bool: Into<T>,
{
    fn with<V, F: FnOnce(&bool) -> V>(&self, data: &T, f: F) -> V {
        let tmp = !<T as Into<bool>>::into(*data);
        f(&tmp)
    }
    fn with_mut<V, F: FnOnce(&mut bool) -> V>(&self, data: &mut T, f: F) -> V {
        let mut tmp = !<T as Into<bool>>::into(*data);
        let out = f(&mut tmp);
        *data = (!tmp).into();
        out
    }
}

1 Like