Приведение непримитивного типа в Rust

Я новичок в Rust, поэтому прошу прощения, если это простой вопрос!

У меня есть следующий упрощенный сценарий, в котором есть признак и два типа структур (TypeA и TypeB), реализующие этот признак. Затем у меня есть вектор признаков, содержащий экземпляры TypeA и TypeB.

Позже я хотел бы перебрать векторные поля и поля доступа, которые принадлежат только TypeA или TypeB, в зависимости от типа. Ниже то, что я написал. Моя попытка состоит в том, чтобы сопоставить тип каждого элемента, а затем преобразовать итератор признака в штучной упаковке в структуру. Это работает со ссылкой &, но как мне добиться изменяемой ссылки &mut.

Для справки & рассмотрим следующее...

use std::any::Any;

trait MyTrait {
    fn do_something(&self);
    fn as_any(&self) -> &dyn Any;
}

struct TypeA{field_a: u16}
impl MyTrait for TypeA {
    fn do_something(&self) {
        println!("TypeA is doing something.");
    }
    fn as_any(&self) -> &dyn Any {
        self
   }
}

struct TypeB{field_b: u16}
impl MyTrait for TypeB {
    fn do_something(&self) {
        println!("TypeB is doing something.");
    }
    fn as_any(&self) -> &dyn Any {
        self
   }
}

fn main() {
    let vec: Vec<Box<dyn MyTrait>> = vec![
        Box::new(TypeA{field_a:10}),
        Box::new(TypeB{field_b:20}),
    ];

    for item in &vec {
        // Downcast the trait object to its concrete type

        if let Some(type_a) = item.as_any().downcast_ref::<TypeA>() {
            type_a.do_something();
            print!("{}", type_a.field_a);
        } else if let Some(type_b) = item.as_any().downcast_ref::<TypeB>() {
            type_b.do_something();
            print!("{}", type_b.field_b);
        } else {
            println!("Unknown type");
        }
    }
}

Однако я хочу изменить поля в пониженном экземпляре, например

type_a.field_a = 100;

@freakish Поскольку эта черта поддерживает приведение к dyn Any, это действительно возможно, хотя и неэффективно и, вероятно, опрометчиво.

cdhowie 04.05.2024 22:55

Если у вас есть только два типа (или любой фиксированный набор типов), вам, вероятно, лучше использовать перечисление.

Chayim Friedman 04.05.2024 22:59

Спасибо всем за ваши комментарии — я немного изменил вопрос, чтобы показать, что я могу добиться приведения обратно к типу экземпляра, а также сосредоточиться на получении изменяемой ссылки на экземпляр. Похоже, что простое добавление &mut не совсем работает, и я недостаточно знаком с Rust, чтобы это исправить. Однако я не сомневаюсь, что есть лучший, более «Rust» способ добиться этого, поэтому, если есть лучший способ, пожалуйста, дайте мне знать!

RedPen 04.05.2024 23:03

Вы используете итерацию по общей ссылке, вы не можете получить из нее изменяемую ссылку и, следовательно, не можете изменить референта. Вам нужно использовать iter_mut (или for item in &mut vec и объявить свой vec с помощью let mut vec), а затем использовать Any::downcast_mut для приведения к конкретной изменяемой ссылке.

Masklinn 05.05.2024 10:40
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
4
147
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы можете сделать это, если добавите способ получить &mut dyn Any по своей черте:

fn as_any_mut(&mut self) -> &mut dyn Any;

Реализация такая же, как as_any:

fn as_any_mut(&mut self) -> &mut dyn Any {
    self
}

Вам также нужно будет сделать vec изменяемым и перебирать &mut vec вместо &vec, и тогда вы сможете сделать это, например:

item.as_any_mut().downcast_mut::<TypeA>()

Это дает вам Option<&mut TypeA>.


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

Большое спасибо, это сработало отлично. Мне любопытно, почему вы рекомендуете перечисление вместо коробочных черт. Это связано с безопасностью, производительностью или просто плохим дизайном? еще раз спасибо

RedPen 04.05.2024 23:13

@RedPen И безопасность, и производительность, и эргономика. Безопасность - использование исчерпывающих совпадений, enum позволяет вам быть уверенным, что вы не забудете обработать ни один случай, а также гарантирует, что вы не попытаетесь выполнить приведение к неправильному типу. Производительность — перечисления не требуют упаковки, что сокращает время кэширования и создания, а приведение вниз с использованием динамической диспетчеризации делает его действительно неэффективным по сравнению с сопоставлением перечислений. Эргономика — приведение к понижению не совсем эргономично, и даже когда оно вам не нужно, перечисления позволяют легко группировать операции над разными типами (проблема с выражением)

Chayim Friedman 04.05.2024 23:37

@RedPen Действительно, их единственный недостаток - это когда есть один большой вариант, и в этом случае все остальные варианты также будут большими, но это тоже можно смягчить с помощью бокса.

Chayim Friedman 04.05.2024 23:38

@RedPen Другой вариант — использовать ваши типы полиморфно, то есть только через члены типажа, без понижающего приведения. Если вам приходится унывать, это признак того, что вы думаете о вещах совершенно неправильно. Сам признак должен определять все, что вам нужно для использования значения. В данном случае это может означать предоставление методов get и set для типажа для содержащегося значения u16. fn value(&self) -> u16; fn set_value(&mut self, value: u16);

cdhowie 04.05.2024 23:45

@RedPen Хотя я, очевидно, не говорю за cdhowie, имейте в виду, что рекомендация против объектов типажей применима только тогда, когда вы выполняете понижение до конкретных типов. В этом случае вам, скорее всего, лучше использовать перечисление. Конечно, есть исключения, и Any существует не зря, но, вероятно, это не должен быть первый инструмент, к которому вы обратитесь.

user4815162342 05.05.2024 00:13

@RedPen, если вы хотите получить более подробную информацию о вопросах проектирования между использованием динамической отправки и перечислений, я бы порекомендовал этот ответ. Вопрос был об OCaml, поэтому примеры приведены на этом языке, но я думаю, что объяснения понятны, даже если вы не знаете OCaml.

jthulhu 05.05.2024 08:43

Другие вопросы по теме