2

I came across a problem which I thought would be perfect to use trait objects for. While I managed to make it work, it looks far more complicated than it should so I would like to know if there is a more optimal way to use trait objects.

Exemplified code:

/* This takes a slice of objects implementing trait and compares each of 
 them with all other objects in the slice, if they interact with each other
 both objects should call a certain function with the other object as a parameter.

 This is used for a collision system, in case you know of a better way to solve this please
 let me know. */

fn foo(objects: &mut [Box<Trait>]) {
    let mut active_objects: Vec<&mut Box<Trait>> = vec!();

    for current in objects.iter_mut() {
        for other in active_objects.iter_mut() {
            if (**current).is_interacting(&***other) {
                current.boo(&mut ***other);
                other.boo(&mut **current);
            }
        }

        active_objects.push(current);
    }
}

trait Trait {
    fn boo(&mut self, other: &mut Trait);

    fn is_interacting(&self, other: & Trait) -> bool;
}

Is there a way I don't have to write something like &*** every time I want to use the actual object?

if (**current).is_interacting(&***other) becomes if current.is_interacting(&***other) as Rust automatically dereferences in this case.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
lncr
  • 826
  • 8
  • 16
  • 1
    You do not need to dereference `current`, it is automatic. – Boiethios Jul 18 '17 at 07:21
  • @Boiethios Thanks :), this takes me from 10 `*` to 8. – lncr Jul 18 '17 at 07:26
  • That's a beginning. There is a problem on `other` because the type is `&mut &mut std::boxed::Box`. – Boiethios Jul 18 '17 at 07:46
  • Yeah, that's true. But I did not find a way to prevent this, as `active_objects` has to be a vector of references, as I would move out of borrowed context otherwise, and I have to `iter_mut` to be able to mutate the traitobject, so while I agree that `&mut &mut` is something undesirable I don't know how to do it otherwise... – lncr Jul 18 '17 at 08:12
  • You could ask the question: "How to visit all pair mutably in a vector?". I would search in this direction: https://doc.rust-lang.org/1.1.0/collections/slice/struct.Permutations.html or https://crates.io/crates/permutohedron/ – Boiethios Jul 18 '17 at 08:47

3 Answers3

3

As red75prime points out, as_mut() is a possibility to take a mutable reference to a Box, which provides an even better solution:

fn foo(objects: &mut [Box<Trait>]) {
    let mut active_objects: Vec<&mut Box<Trait>> = vec!();

    for current in objects.iter_mut() {
        for other in active_objects.iter_mut() {
            let current = current.as_mut();
            let other = other.as_mut();

            if current.is_interacting(other) {
                current.boo(other);
                other.boo(current);
            }
        }

        active_objects.push(current);
    }
}

trait Trait {
    fn boo(&mut self, other: &mut Trait);

    fn is_interacting(&self, other: &Trait) -> bool;
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Viktor Chvátal
  • 463
  • 9
  • 19
  • Nice solution, but I am convinced there is a simpler solution to do what OP wants. – Boiethios Jul 18 '17 at 08:20
  • Well, I agree ;) This is just one step further – Viktor Chvátal Jul 18 '17 at 08:22
  • `let current = current.as_mut(); ...` works too. And I'm not sure it can be simpler. – red75prime Jul 18 '17 at 08:26
  • It definitely looks better than my first test, I did some testing and it is actually faster than my original function by about 4% which I most certainly did not expect. In case there won't be a better answer I will accept yours. Thanks! – lncr Jul 18 '17 at 08:52
  • `as_mut` and `borrow_mut` don't seem to effect process speed at all, so using `as_mut` should be cleaner due to the fact that I don't have to `use` anything. – lncr Jul 18 '17 at 09:14
2

It's not necessary to keep references to Box objects in the active_objects vector. This should work and eliminates most of the dereferencing:

fn foo(objects: &mut [Box<Trait>]) {
    let mut active_objects: Vec<&mut Trait> = vec!();

    for current in objects.iter_mut() {
        let current = current.as_mut();
        for other in active_objects.iter_mut() {
            if current.is_interacting(*other) {
                current.boo(*other);
                other.boo(current);
            }
        }

        active_objects.push(current);
    }
}
Florian Weimer
  • 32,022
  • 3
  • 48
  • 92
  • 1
    Nice, but it's worth to remember that `&mut Trait` is a fat pointer that takes twice as much space as `&mut Box`. – red75prime Jul 18 '17 at 09:24
  • 2
    But that means that it uses less indirections so it could provide faster access – Viktor Chvátal Jul 18 '17 at 09:27
  • I compared the speed of both answers, this one is 10 to 15% faster than the other functions, which is impressive, the best solution yet! Thanks – lncr Jul 18 '17 at 09:33
  • @ViktorChvátal, right, but as always it's better to bench. Decreased number of memory accesses could be offset by increased cache pressure. – red75prime Jul 18 '17 at 09:44
1

You can remove all dereferences in your code:

fn foo(objects: &mut [Box<Trait>]) {
    let mut active_objects: Vec<&mut Box<Trait>> = vec![];

    for current in objects.iter_mut() {
        for other in active_objects.iter_mut() {
            if current.is_interacting(other) {
                current.boo(other);
                other.boo(current);
            }
        }

        active_objects.push(current);
    }
}

You enable this by implementing the trait itself for references and boxes to the type:

impl<'a, T> Trait for &'a mut T
where
    T: Trait + ?Sized,
{
    fn boo(&mut self, other: &mut Trait) {
        (**self).boo(other)
    }

    fn is_interacting(&self, other: &Trait) -> bool {
        (**self).is_interacting(other)
    }
}

impl<T> Trait for Box<T>
where
    T: Trait + ?Sized,
{
    fn boo(&mut self, other: &mut Trait) {
        (**self).boo(other)
    }

    fn is_interacting(&self, other: &Trait) -> bool {
        (**self).is_interacting(other)
    }
}

This is a one-time piece of code that you add near your trait definition allowing the call site to be cleaner.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366