Можно ли параметризовать функцию generi c с помощью признака? - PullRequest
1 голос
/ 07 февраля 2020

Можно ли передать черты в качестве параметров для обобщенных c функций, подобных этой?

trait Fnord {
    fn do_it(&self) -> i32 { 42 }
}

impl Fnord for i32 {}

fn iter_as<'a, T>(objs: &'a [i32]) -> impl Iterator<Item = & 'a dyn T>
{
    objs.iter().map(|o| o as &dyn T)
}

fn main() {
    let objs: Vec<i32> = vec![1, 2, 3];

    // Calls would look like this
    for s in iter_as::<Fnord>(&objs) {
        println!("{}", s.do_it());
    }
}

Это вызывает у меня следующие ошибки:

error[E0404]: expected trait, found type parameter `T`
 --> src/lib.rs:7:69
  |
7 | fn iter_as<'a, T>(objs: &'a [i32]) -> impl Iterator<Item = & 'a dyn T>
  |                                                                     ^ not a trait

error[E0404]: expected trait, found type parameter `T`
 --> src/lib.rs:9:35
  |
9 |     objs.iter().map(|o| o as &dyn T)
  |                                   ^ not a trait

warning: trait objects without an explicit `dyn` are deprecated
  --> src/lib.rs:16:24
   |
16 |     for s in iter_as::<Fnord>(&objs) {
   |                        ^^^^^ help: use `dyn`: `dyn Fnord`
   |
   = note: `#[warn(bare_trait_objects)]` on by default

То есть может iter_as принять признак в качестве универсального c параметра, чтобы он мог вернуть итерацию этой черты? Я довольно долго искал ответ, но в этот момент я чувствую, что, возможно, задаю не тот вопрос.

Фон такой. У меня есть структура с несколькими векторами разных конкретных типов, каждый из которых реализует одинаковые черты. Я хотел бы, чтобы в структуре имелась функция, которая может возвращать итерируемое по всем хранимым объектам как любую из их общих характеристик. iter_as выше является упрощенной версией этой (условной) функции. Возможно, я просто неловко подхожу к этому из-за ржавчины (то есть, может быть, я слишком много думаю как программист на C ++), так что альтернативный идиоматический подход c тоже был бы великолепен.

1 Ответ

2 голосов
/ 07 февраля 2020

T должен быть конкретным типом, а не чертой. Я могу подумать о том, что вам ближе то, что вы ищете:

trait Fnord {
    fn do_it(&self) -> i32;
}

impl Fnord for i32 {
    fn do_it(&self) -> i32 {
        *self
    }
}

impl<'a> From<&'a i32> for &'a dyn Fnord {
    fn from(i: &'a i32) -> Self {
        i as _
    }
}

fn iter_as<'a, T, TObj>(objs: &'a [T]) -> impl Iterator<Item = TObj> + 'a
where
    TObj: 'a,
    TObj: From<&'a T>,
{
    objs.iter().map(|o| o.into())
}

fn main() {
    let objs: Vec<i32> = vec![1, 2, 3];

    for s in iter_as::<i32, &dyn Fnord>(&objs) {
        println!("{}", s.do_it()); // 1 2 3
    }
}

Я не уверен, что вы выбрали идиоматический c Rust способ сделать это: так как вы Зная конкретные типы вашего объекта, вы можете написать его следующим образом:

trait Fnord {
    fn do_it(&self) -> i32;
}

impl Fnord for i32 {
    fn do_it(&self) -> i32 {
        *self
    }
}

impl<'a> From<&'a i32> for &'a dyn Fnord {
    fn from(i: &'a i32) -> Self {
        i as _
    }
}

struct YourObject {
    v1: Vec<i32>,
    v2: Vec<i32>,
}

impl YourObject {
    fn iter_as<'a, T>(&'a self) -> impl Iterator<Item = T> + 'a
    where
        T: From<&'a i32>, // Add the other bounds you need
    {
        self.v1
            .iter()
            .map(|o| o.into())
            .chain(self.v2.iter().map(|o| o.into()))
    }
}

fn main() {
    let obj = YourObject {
        v1: vec![1, 2],
        v2: vec![3],
    };

    for s in obj.iter_as::<&dyn Fnord>() {
        println!("{}", s.do_it()); // 1 2 3
    }
}

С помощью макроса:

macro_rules! impl_from_for_dyn_trait {
    ( $concrete:ty, $trait:path ) => {
        impl<'a> From<&'a $concrete> for &'a dyn $trait {
            fn from(c: &'a $concrete) -> Self {
                c as _
            }
        }
    }
}

impl_from_for_dyn_trait!(i32, Fnord);
можно сократить шаблон реализации From.
...