Two blanket implementations for different classes of objects

I would like to have two different implementations of MyMainTrait, one for simple objects, and other for structs. Negative trait bounds would help, but they are not here. There are "negative_impls" but they do not help.

#![feature(negative_impls)]

trait MyMainTrait {
    fn do_something(&self) {
        println!("simple object {:?}", self);
    }
}

trait SimpleObject: std::fmt::Debug {}
impl<T: SimpleObject> !ComplexObject for T {}

impl<T: SimpleObject> MyMainTrait for T {
    fn do_something(&self) {
        println!("simple object {:?}", self);
    }
}
impl SimpleObject for i32 {}
impl SimpleObject for &str {}

// note that there is no Debug for Complex object and this is intentional
trait ComplexObject {
    const FIELDS: &'static [&'static str];
    fn do_something_to_child(&self, child_name: &str);
}
impl<T: ComplexObject> !SimpleObject for T {}
impl<T: ComplexObject> MyMainTrait for T {
    fn do_something(&self) {
        for child in Self::FIELDS {
            self.do_something_to_child(child);
        }
    }
}

struct Foo {
    field: i32
}
impl ComplexObject for Foo {
    const FIELDS: &'static [&'static str] = &["field"];
    
    fn do_something_to_child(&self, child_name: &str) {
        if child_name == "field" {
            self.field.do_something();
        }
    }
}

fn do_it(it: &impl MyMainTrait) {
    it.do_something();
}

fn main() {
    do_it(&100);
    do_it(&"hello world");
    do_it(&Foo {field:100});
}

playground

Specialization doesn't exist yet. See Tracking issue for specialization (RFC 1210) · Issue #31844 · rust-lang/rust (github.com) for e.g. min_specialization which might possibly address some of these kinds of challenges.

2 Likes

You can use a slightly different design to reach the same goals on stable Rust no less

The main idea here is that you can use associated types to ensure that two traits are disjoint (the same type can't implement both traits). Then you can use the fact that if two different impls have different generic parameters (i.e. MyMainTraitHelper<SimpleType> vs MyMainTraitHelper<ComplexType>), then they don't conflict to create multiple blanket impls.

The main downside is that it's more complex to implement, but fortunately, most of that complexity can be hidden away.
It would be nice if this RFC was revived: Disjointness based on associated types. by withoutboats · Pull Request #1672 · rust-lang/rfcs · GitHub :weary:

4 Likes

Thank you for the idea.
I made slightly different implementation based on it:
playground

The usage is as follows:

// simple types
impl MyMainTrait for i32 {
    type BlanketType = SimpleType<Self>;
}
impl MyMainTrait for &str {
    type BlanketType = SimpleType<Self>;
}

// struct types
struct Foo {
    field: i32,
}

impl MyMainTrait for Foo {
    type BlanketType = StructType<Foo>;
}

impl StructTrait for Foo {
    const FIELDS: &'static [&'static str] = &["field"];
    
    fn do_something_to_field(&self, field: &str) {
        match field {
            "field" => self.field.do_something(),
            _ => panic!("unknown field"),
        }
    }
}

// custom types with their own implementation
struct SomethingNotUsingBlanket {
    field: i32
}

impl MyMainTrait for SomethingNotUsingBlanket {
    type BlanketType = Self;
}

impl BlanketMainTrait for SomethingNotUsingBlanket {
    type Target = Self;
    
    fn do_something(t: &Self) {
        println!("Something special: {}", t.field);
    }
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.