Подтвердить что ты не робот

Как сопоставить разработчики свойств

У меня есть черта, которая реализована некоторыми структурами. Я хочу написать сопоставление с образцом, где я могу обработать каждый возможный случай:

trait Base {}

struct Foo {
    x: u32,
}
struct Bar {
    y: u32,
}

impl Base for Foo {}
impl Base for Bar {}

fn test(v: bool) -> Box<Base + 'static> {
    if v {
        Box::new(Foo { x: 5 })
    } else {
        Box::new(Bar { y: 10 })
    }
}

fn main() {
    let f: Box<Base> = test(true);

    match *f {
        Foo { x } => println!("it was Foo: {}!", x),
        Bar { y } => println!("it was Bar: {}!", y),
    }
}

(Детская площадка)

Я получаю эту ошибку компиляции:

error[E0308]: mismatched types
  --> src/main.rs:25:9
   |
25 |         Foo { x } => println!("it was Foo: {}!", x),
   |         ^^^^^^^^^ expected trait Base, found struct 'Foo'
   |
   = note: expected type 'dyn Base'
              found type 'Foo'

error[E0308]: mismatched types
  --> src/main.rs:26:9
   |
26 |         Bar { y } => println!("it was Bar: {}!", y),
   |         ^^^^^^^^^ expected trait Base, found struct 'Bar'
   |
   = note: expected type 'dyn Base'
              found type 'Bar'
4b9b3361

Ответ 1

Ты не можешь. Черты не поддерживают downcasting - Rust не является языком, основанным на наследовании/подтипах, и дает вам другой набор абстракций. Более того, то, что вы хотите сделать, является необоснованным - черты открыты (каждый может реализовать их для чего угодно), так что даже если в вашем случае match *f охватывает все возможные случаи, в общем, компилятор не может этого знать.

У вас есть два варианта здесь. Если вы знаете множество структур, реализующих вашу черту заранее, просто используйте enum, это идеальный инструмент для этого. Они позволяют статически сопоставлять закрытый набор вариантов:

enum FooBar {
    Foo(u32),
    Bar(u32),
}

fn test(v: bool) -> FooBar {
    if v {
        FooBar::Foo(5)
    } else {
        FooBar::Bar(10)
    }
}

fn main() {
    let f: FooBar = test(true);

    // Now that we have a 'Box<Base>' ('*f' makes it a 'Base'),
    // let handle different cases:
    match f {
        FooBar::Foo(x) => println!("it was Foo: {}!", x),
        FooBar::Bar(y) => println!("it was Bar: {}!", y),
    }
}

(Детская площадка)

Это, безусловно, самый простой способ, и он всегда должен быть предпочтительным.

Другой способ - использовать Any черту. Это средство для безопасной передачи типов с характерных объектов на обычные типы:

use std::any::Any;

struct Foo {
    x: u32,
}
struct Bar {
    y: u32,
}

fn test(v: bool) -> Box<Any + 'static> {
    if v {
        Box::new(Foo { x: 5 })
    } else {
        Box::new(Bar { y: 10 })
    }
}

fn main() {
    let f: Box<Any> = test(true);

    match f.downcast_ref::<Foo>() {
        Some(&Foo { x }) => println!("it was Foo: {}!", x),
        None => match f.downcast_ref::<Bar>() {
            Some(&Bar { y }) => println!("it was Bar: {}!", y),
            None => unreachable!(),
        },
    }

    // it will be nicer when 'if let' lands
    //    if let Some(ref Foo { x }) = f.downcast_ref::<Foo>() {
    //        println!("it was Foo: {}!", x);
    //    } else if let Some(ref Bar { y }) = f.downcast_ref::<Bar>() {
    //        println!("it was Bar: {}!", y);
    //    } else { unreachable!() }
}

(Детская площадка)

В идеале должно быть возможно написать что-то вроде этого:

trait Base: Any {}

impl Base for Foo {}
impl Base for Bar {}

и затем использовать Base в коде, но это невозможно сделать сейчас, потому что наследование признаков не работает с объектами признаков (например, невозможно перейти из Box<Base> в Base<Any>).

Ответ 2

Вы можете использовать контейнер match_cast:

match_cast!( any {
    val as Option<u8> => {
        format!("Option<u8> = {:?}", val)
    },
    val as String => {
        format!("String = {:?}", val)
    },
    val as &'static str => {
        format!("&'static str = {:?}", val)
    },
});

match_down!( any {
    Bar { x } => { x },
    Foo { x } => { x },
});

Ответ 3

Я предлагаю шаблон посетителя, чтобы соответствовать по чертам. Эта модель из ООП, но она сияет во многих ситуациях. Более того, он более эффективен, избегая принижения.

Вот такой фрагмент:

struct Foo{ value: u32 }
struct Bar{ value: u32 }

trait Base<T> {
    fn accept(&self, v: &dyn Visitor<Result = T>) -> T ;
}
impl <T>Base<T> for Foo {
    fn accept(&self, v: &dyn Visitor<Result = T>) -> T {
        v.visit_foo(&self) 
    }
}
impl <T>Base<T> for Bar {
    fn accept(&self, v: &dyn Visitor<Result = T>) -> T {
        v.visit_bar(&self) 
    }
}

trait Visitor {
    type Result;
    fn visit_foo(&self, foo: &Foo) -> Self::Result;
    fn visit_bar(&self, bar: &Bar) -> Self::Result;
}

struct StringVisitor {}
impl Visitor for StringVisitor {
    type Result = String;
    fn visit_foo(&self, foo: &Foo) -> String {
        format!("it was Foo: {:}!", foo.value)
    }
    fn visit_bar(&self, bar: &Bar) -> String {
        format!("it was Bar: {:}!", bar.value)
    }
}
fn test<T>(v: bool) -> Box<dyn Base<T>> {
    if v {
        Box::new(Foo{value: 5})
    } else {
        Box::new(Bar{value: 10}) 
    }
}
fn main() {
    let f = test(true);
    println!("{:}", f.accept( &StringVisitor{} ));
}