Есть ли способ избежать разыменования с трейт-объектами?

Я столкнулся с проблемой, для которой, по моему мнению, идеально было бы использовать трейт-объекты. Хотя мне удалось заставить его работать, он выглядит намного сложнее, чем должен, поэтому я хотел бы знать, есть ли более оптимальный способ использования типовых объектов.

Пример кода:

/* 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;
}

Есть ли способ, которым мне не нужно писать что-то вроде &*** каждый раз, когда я хочу использовать фактический объект?

if (**current).is_interacting(&***other) становится if current.is_interacting(&***other), так как Rust автоматически разыменовывает в этом случае.


person lncr    schedule 18.07.2017    source источник
comment
Вам не нужно разыменовывать current, это происходит автоматически.   -  person Boiethios    schedule 18.07.2017
comment
@Boiethios Спасибо :), это переводит меня с 10 * на 8.   -  person lncr    schedule 18.07.2017
comment
Это начало. Проблема с other, потому что тип &mut &mut std::boxed::Box<Trait>.   -  person Boiethios    schedule 18.07.2017
comment
Да, это правда. Но я не нашел способа предотвратить это, так как active_objects должен быть вектором ссылок, так как в противном случае я бы вышел из заимствованного контекста, и я должен iter_mut иметь возможность видоизменять объект типажей, так что пока я согласен с тем, что &mut &mut это что-то нежелательное, иначе я не знаю, как это сделать...   -  person lncr    schedule 18.07.2017
comment
Вы можете задать вопрос: Как посетить всю пару мутабельно в векторе?. Я бы искал в этом направлении: doc.rust-lang .org/1.1.0/collections/slice/ или crates.io/crates/permutohedron   -  person Boiethios    schedule 18.07.2017


Ответы (3)


Вы можете удалить все разыменования в коде:

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);
    }
}

Вы включаете это, реализуя сам трейт для ссылок и блоков к типу:

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)
    }
}

Это одноразовый фрагмент кода, который вы добавляете рядом с определением вашего признака, что позволяет сделать сайт вызова более чистым.

person Shepmaster    schedule 18.07.2017
comment
Спасибо, я не знал, что можно использовать impl на &. - person lncr; 18.07.2017

Как указывает red75prime, as_mut() является возможность использовать изменяемую ссылку на Box, что обеспечивает еще лучшее решение:

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;
}
person Viktor Chvátal    schedule 18.07.2017
comment
Хорошее решение, но я убежден, что есть более простое решение для выполнения того, что хочет ОП. - person Boiethios; 18.07.2017
comment
Что ж, я согласен ;) Это всего лишь еще один шаг вперед - person Viktor Chvátal; 18.07.2017
comment
let current = current.as_mut(); ... тоже работает. И я не уверен, что это может быть проще. - person red75prime; 18.07.2017
comment
Это определенно выглядит лучше, чем мой первый тест, я провел некоторые тесты, и на самом деле это быстрее, чем моя исходная функция, примерно на 4%, чего я совершенно точно не ожидал. В случае, если не будет лучшего ответа, я приму ваш. Спасибо! - person lncr; 18.07.2017
comment
as_mut и borrow_mut, кажется, вообще не влияют на скорость процесса, поэтому использование as_mut должно быть чище из-за того, что мне не нужно ничего use. - person lncr; 18.07.2017

Нет необходимости хранить ссылки на объекты Box в векторе active_objects. Это должно работать и устраняет большую часть разыменования:

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);
    }
}
person Florian Weimer    schedule 18.07.2017
comment
Красиво, но стоит помнить, что &mut Trait — это толстый указатель, который занимает в два раза больше места, чем &mut Box<Trait>. - person red75prime; 18.07.2017
comment
Но это означает, что он использует меньше косвенных обращений, чтобы обеспечить более быстрый доступ. - person Viktor Chvátal; 18.07.2017
comment
Я сравнил скорость обоих ответов, этот на 10-15% быстрее, чем другие функции, что впечатляет, лучшее решение! Спасибо - person lncr; 18.07.2017
comment
@ViktorChvátal, верно, но как всегда лучше лежать. Уменьшение количества обращений к памяти может быть компенсировано увеличением нагрузки на кэш. - person red75prime; 18.07.2017