Я новичок в 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;
Если у вас есть только два типа (или любой фиксированный набор типов), вам, вероятно, лучше использовать перечисление.
Спасибо всем за ваши комментарии — я немного изменил вопрос, чтобы показать, что я могу добиться приведения обратно к типу экземпляра, а также сосредоточиться на получении изменяемой ссылки на экземпляр. Похоже, что простое добавление &mut не совсем работает, и я недостаточно знаком с Rust, чтобы это исправить. Однако я не сомневаюсь, что есть лучший, более «Rust» способ добиться этого, поэтому, если есть лучший способ, пожалуйста, дайте мне знать!
Вы используете итерацию по общей ссылке, вы не можете получить из нее изменяемую ссылку и, следовательно, не можете изменить референта. Вам нужно использовать iter_mut (или for item in &mut vec и объявить свой vec с помощью let mut vec), а затем использовать Any::downcast_mut для приведения к конкретной изменяемой ссылке.

Вы можете сделать это, если добавите способ получить &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 И безопасность, и производительность, и эргономика. Безопасность - использование исчерпывающих совпадений, enum позволяет вам быть уверенным, что вы не забудете обработать ни один случай, а также гарантирует, что вы не попытаетесь выполнить приведение к неправильному типу. Производительность — перечисления не требуют упаковки, что сокращает время кэширования и создания, а приведение вниз с использованием динамической диспетчеризации делает его действительно неэффективным по сравнению с сопоставлением перечислений. Эргономика — приведение к понижению не совсем эргономично, и даже когда оно вам не нужно, перечисления позволяют легко группировать операции над разными типами (проблема с выражением)
@RedPen Действительно, их единственный недостаток - это когда есть один большой вариант, и в этом случае все остальные варианты также будут большими, но это тоже можно смягчить с помощью бокса.
@RedPen Другой вариант — использовать ваши типы полиморфно, то есть только через члены типажа, без понижающего приведения. Если вам приходится унывать, это признак того, что вы думаете о вещах совершенно неправильно. Сам признак должен определять все, что вам нужно для использования значения. В данном случае это может означать предоставление методов get и set для типажа для содержащегося значения u16. fn value(&self) -> u16; fn set_value(&mut self, value: u16);
@RedPen Хотя я, очевидно, не говорю за cdhowie, имейте в виду, что рекомендация против объектов типажей применима только тогда, когда вы выполняете понижение до конкретных типов. В этом случае вам, скорее всего, лучше использовать перечисление. Конечно, есть исключения, и Any существует не зря, но, вероятно, это не должен быть первый инструмент, к которому вы обратитесь.
@RedPen, если вы хотите получить более подробную информацию о вопросах проектирования между использованием динамической отправки и перечислений, я бы порекомендовал этот ответ. Вопрос был об OCaml, поэтому примеры приведены на этом языке, но я думаю, что объяснения понятны, даже если вы не знаете OCaml.
@freakish Поскольку эта черта поддерживает приведение к
dyn Any, это действительно возможно, хотя и неэффективно и, вероятно, опрометчиво.