1

I have two types A and B that implement the Fooable trait. I have an array of As, and an array of Bs, and create an iterator over the two arrays, the iterator having Item type &dyn Fooable. I want to write a test to confirm that the iterator outputs the correct objects, something like:

struct C {
    as_array: [A; 2],
    bs_arry: [B; 2],
}

impl C {
    fn contents_iter(&self) -> FoosIterator {
        FoosIterator {
            a_iter: (&self.as_array).into_iter(),
            b_iter: (&self.bs_arry).into_iter(),
        }
    }
}

struct FoosIterator<'a> {
    a_iter: <&'a [A; 2] as IntoIterator>::IntoIter,
    b_iter: <&'a [B; 2] as IntoIterator>::IntoIter,
}

impl<'a> Iterator for FoosIterator<'a> {
    type Item = &'a dyn Fooable;
    fn next(&mut self) -> Option<Self::Item> {
        match self.a_iter.next() {
            Some(upper_slot) => Some(upper_slot),
            None => self.b_iter.next().map(|x| x as &dyn Fooable),
        }
    }
}

trait Fooable: std::fmt::Debug {
    fn calculate_num(&self) -> i32;
}

#[derive(PartialEq, Debug)]
struct A {
    value: i32,
}

impl Fooable for A {
    fn calculate_num(&self) -> i32 {
        self.value
    }
}

#[derive(PartialEq, Debug)]
struct B {
    value_1: i32,
    value_2: i32,
}

impl Fooable for B {
    fn calculate_num(&self) -> i32 {
        self.value_1 * 5 + self.value_2
    }
}

#[test]
fn test_contents_iter() {
    let c = C {
        as_array: [A { value: 3 }, A { value: 5 }],
        bs_arry: [
            B {
                value_1: 3,
                value_2: 1,
            },
            B {
                value_1: 5,
                value_2: 2,
            },
        ],
    };
    let mut iter = c.contents_iter();
    assert_eq!(
        *iter
            .next()
            .expect("Should have initial element from iterator"),
        A { value: 3 }
    );
    assert_eq!(
        *iter
            .next()
            .expect("Should have second element from iterator"),
        A { value: 5 }
    );
    assert_eq!(
        *iter
            .next()
            .expect("Should have third element from iterator"),
        B {
            value_1: 3,
            value_2: 1
        }
    );
    assert_eq!(
        *iter
            .next()
            .expect("Should have fourth element from iterator"),
        B {
            value_1: 5,
            value_2: 2
        }
    );
}

The problem is that objects of type &dyn Fooable do not implement the PartialEq trait, so cannot be compared for equality:

error[E0369]: binary operation `==` cannot be applied to type `dyn Fooable`
  --> src/lib.rs:73:5
   |
73 | /     assert_eq!(
74 | |         *iter
75 | |             .next()
76 | |             .expect("Should have initial element from iterator"),
77 | |         A { value: 3 }
78 | |     );
   | |      ^
   | |      |
   | |______dyn Fooable
   |        A
   |
   = note: an implementation of `std::cmp::PartialEq` might be missing for `dyn Fooable`
   = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

As I understand it (for instance from How to test for equality between trait objects?) there is no way to implement this trait for a dynamic type. Is there any way to achieve something like the aim here? I suppose one could instead expose some of the data constituting objects of type A and B in the Fooable trait, and use this to check that the objects outputted are the correct ones (in my actual use case the As and Bs are more complicated than in my toy example above).

McDuffin
  • 558
  • 4
  • 9
  • It looks like your question might be answered by the answers of [How to get a reference to a concrete type from a trait object?](https://stackoverflow.com/q/33687447/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Oct 15 '19 at 14:28
  • In my use case the "create_vector" object does not have a static lifetime, which I believe means that one cannot downcast via Any? I've edited the question accordingly – McDuffin Oct 15 '19 at 15:56
  • 2
    *static lifetimes seems unattractive in a language whose calling card is safe, efficient memory management* — this appears to be a non-sequitur. These two things, while related, aren't relevant to each other in this situation. – Shepmaster Oct 15 '19 at 17:00

1 Answers1

0

In the absence of any better ideas, one possible workaround is to make use of the Debug trait for As and Bs: testing the formatted debugging output for equality is often equivalent to the intended notion of equality between As and Bs.

/* ... */
assert_eq!(
    format!("{:?}", *iter
        .next()
        .expect("Should have initial element from iterator")),
    format("{:?}", A { value: 3 })
);
assert_eq!(
    format!("{:?}", *iter
        .next()
        .expect("Should have second element from iterator")),
    format("{:?}", A { value: 5 })
);
assert_eq!(
    format!("{:?}", *iter
        .next()
        .expect("Should have third element from iterator")),
    format("{:?}",         
    B {
        value_1: 3,
        value_2: 1
    })
);
assert_eq!(
    format!("{:?}", *iter
        .next()
        .expect("Should have third element from iterator")),
    format("{:?}",         
    B {
        value_1: 5,
        value_2: 2
    })
);
McDuffin
  • 558
  • 4
  • 9