Как соответствовать чертам разработчиков

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

trait Base {}

struct Foo { x: uint }
struct Bar { y: uint }

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

fn test(v: bool) -> Box<Base + 'static> {
    // Let's pretend there's real logic that determines what to return.
    if v {
        box Foo { x: 5 }
    } else {
        box Bar { y: 10 }
    }
}

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

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

(попробуйте онлайн: http://is.gd/YuBkPF)

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

несоответствующие типы: ожидается Base, нашел структуру структуры

2 ответа

Решение

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

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

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

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

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

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

(попробуйте здесь)

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

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

use std::any::{Any, AnyRefExt};

struct Foo { x: uint }
struct Bar { y: uint }

fn test(v: bool) -> Box<Any + 'static> {  // '
    if v {
        box Foo { x: 5 }
    } else {
        box 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(&Foo { x }) = f.downcast_ref::<Foo>() {
//        println!("it was Foo: {}!", x);
//    } else if let Some(&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>).

Вы можете использовать мой 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 },
});

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

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

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{} ));
}
Другие вопросы по тегам