Получение обратных вызовов с помощью `impl Trait` и передача им экзистенциальных значений - PullRequest
0 голосов
/ 30 сентября 2018

При определении API для обратных вызовов обработчика (например, обработка команд оболочки или сетевых запросов) я хотел бы скрыть подробности реализации от сигнатуры обратного вызова - например, я хотел бы принять обратные вызовы вида

fn c(data: impl Iterator<Item = i32>) -> ()

Хотя я легко могу выразить сами обратные вызовы, используя синтаксис аргумента impl Trait или как fn c<I: Iterator<...>>(data: I) -> (), я не могу принять их в общем, потому что мой процессор не универсален на I, а экзистенциальн.

Я мог бы сделать общие части обработки на I, а затем сказать, что I: Iterator<Item = i32>:

/// A non-working callback that receives an iterator.
use std::iter::*;
use std::marker::PhantomData;

struct Processor<CB, I> {
    callback: CB,
    _i: PhantomData<I>,
}

// Here it'd be nice to say that it won't implement it for all I, but there exists an (unnamable) I
// for which it's implemented.
impl<CB, I> Processor<CB, I>
where
    CB: FnMut(I) -> (),
    I: Iterator<Item = i32>,
{
    fn process(self) {
        let a = [23, 42].iter().map(|i| i + 1);
        let mut cb = self.callback;
        cb(a)
    }
}

fn c(data: impl Iterator<Item = i32>) {
    println!("Data:");
    for i in data {
        println!("Item: {}", i);
    }
}

fn main() {
    let p = Processor {
        callback: c,
        _i: PhantomData,
    };
    p.process()
}

детская площадка

Это не такпоскольку реализация не является общей для I, и компилятор выражает свое недовольство ложью:

error[E0308]: mismatched types
  --> src/main.rs:20:12
   |
20 |         cb(a)
   |            ^ expected type parameter, found struct `std::iter::Map`
   |
   = note: expected type `I`
              found type `std::iter::Map<std::slice::Iter<'_, {integer}>, [closure@src/main.rs:18:37: 18:46]>`

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

/// A non-working callback that receives an iterator.
use std::iter::*;

struct Processor<CB> {
    callback: CB,
}

impl<CB> Processor<CB>
where
    CB: FnMut(Map<std::slice::Iter<'static, i32>, FnOnce(i32) -> i32>) -> (),
{
    fn process(self) {
        let a = [23, 42].iter().map(|i| i + 1);
        let mut cb = self.callback;
        cb(a)
    }
}

fn c(data: impl Iterator<Item = i32>) {
    println!("Data:");
    for i in data {
        println!("Item: {}", i);
    }
}

fn main() {
    let p = Processor { callback: c };
    p.process()
}

детская площадка

Это все равно не работает, еслиЗдесь задействован лямбда-тип:

error[E0277]: the size for values of type `(dyn std::ops::FnOnce(i32) -> i32 + 'static)` cannot be known at compilation time
  --> src/main.rs:8:1
   |
8  | / impl<CB> Processor<CB>
9  | | where
10 | |     CB: FnMut(Map<std::slice::Iter<'static, i32>, FnOnce(i32) -> i32>) -> (),
11 | | {
...  |
16 | |     }
17 | | }
   | |_^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `(dyn std::ops::FnOnce(i32) -> i32 + 'static)`
   = note: to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html#dynamically-sized-types-and-sized>
   = note: required by `std::iter::Map`

error[E0277]: the size for values of type `(dyn std::ops::FnOnce(i32) -> i32 + 'static)` cannot be known at compilation time
  --> src/main.rs:12:5
   |
12 | /     fn process(self) {
13 | |         let a = [23, 42].iter().map(|i| i + 1);
14 | |         let mut cb = self.callback;
15 | |         cb(a)
16 | |     }
   | |_____^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `(dyn std::ops::FnOnce(i32) -> i32 + 'static)`
   = note: to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html#dynamically-sized-types-and-sized>
   = note: required by `std::iter::Map`

Какой идиоматический способ обойти это?Возможно ли с другим синтаксисом, который говорит: «Для всех CB это реализует Processor, где тип CB должен быть универсальным в своем аргументе, и я могу выбрать его тип»?Что-то вроде

impl<CB> Processor<CB>
where
    CB: FnMut(impl Iterator<Item = i32>) -> (),

Учитывая, что я стремлюсь к встраиваемой среде, я ищу решения, не использующие стандартное управление, поэтому такие подходы, как Box<...>, не решат проблему.

1 Ответ

0 голосов
/ 02 октября 2018

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

Изюминкой является функция черты

fn call_now<T: Iterator<Item=i32>>(&mut self, data: T) -> ();

, и пример полностью адаптированного кода выглядит следующим образом:

use std::iter::*;

trait Callback {
    fn call_now<T: Iterator<Item=i32>>(&mut self, data: T) -> ();
}

struct Processor<CB>
{
    callback: CB
}

impl<CB: Callback> Processor<CB> where
{
    fn process(&mut self) {
        let a = [23, 42].iter().map(|i| i + 1);
        self.callback.call_now(a);
    }
}

struct C();
impl Callback for C {
    fn call_now<T: Iterator<Item=i32>>(&mut self, data: T) {
        println!("Data:");
        for i in data {
            println!("Item: {}", i);
        }
    }
}

fn main() {
    let mut p = Processor {
        callback: C(),
    };
    p.process()
}

( пример на игровой площадке )

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

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