Укажите признак `Fn`, связанный с определением структуры, без фиксации одного из параметров` Fn` - PullRequest
0 голосов
/ 04 июня 2018

У меня есть структура, которая содержит объект функции:

struct Foo<F> {
    func: F,
}

Я хочу добавить черту Fn, связанную с определением структуры.Проблема в том, что я забочусь о первом параметре (он должен быть i32), но не о втором.На самом деле я хочу написать что-то вроде этого:

struct Foo<F> 
where
    ∃ P so that F: Fn(i32, P),
{
    func: F,
}

Итак, на английском языке: тип F должен быть функцией, которая принимает два параметра, первый из которых - i32 (ивторой может быть чем угодно).Синтаксис выше, очевидно, недействителен.Я подумал о трех возможных решениях:

  1. Синтаксис for<> здесь не поможет.Помимо того факта, что он еще не работает для не-временного параметра, он универсален («для всех») и не является экзистенциальным («существует»).Вот и все.

  2. Другая возможность - добавить параметр типа в структуру.Мне уже не нравится это решение, потому что параметр по своей природе не принадлежит структуре.

    struct Foo<F, P> 
    where
        F: Fn(i32, P),
    {
        func: F,
    }
    

    Но это не работает: параметр P не используется, кроме как в where связано, поэтому компилятор жалуется.

    Эту проблему можно решить, добавив поле PhantomData<P>, но в этом нет необходимости, и, что более важно, пользователи больше не могут легко использовать синтаксис конструктора структуры.

  3. Наконец я попробовал это:

    struct Foo<F> 
    where
        F: Fn(i32, _),
    {
        func: F,
    }
    

    Но это также не работает:

    error[E0121]: the type placeholder `_` is not allowed within types on item signatures
     --> src/main.rs:3:20
      |
    3 |         F: Fn(i32, _),
      |                    ^ not allowed in type signatures
    

Есть ли способ достичь того, что яхотите?


Примечание : Почему я хочу, чтобы черта уже была привязана к структуре вместо просто блоков impl, где это важно?

Во-первых, как только RFC «подразумеваемых границ признака» реализован, это позволяет мне исключить дублирующиеся границы признака во всех блоках impl.Во-вторых, с этой границей это помогает компилятору с его выводом типа.Учтите это:

struct Foo<F, T> 
where
    F: Fn(T, _),
{
    data: T,
    F: F,
}

Если бы граница была возможной (я попробовал это с PhantomData «решением» выше), компилятор мог бы легче определить тип первого аргумента замыкания.Если границы признака указываются только для блоков impl, у компилятора возникают трудности.

Ответы [ 2 ]

0 голосов
/ 04 июня 2018

Вместо наложения ограничений на структуру, самый простой и лучший подход - наложить ограничения на реализацию всех методов, которые должны будут использовать функцию:

struct Foo<F, T> {
    data: T,
    f: F,
}

impl<F, T> Foo<F, T> {
    fn call_f<P>(&self, arg: P)
    where
        T: Copy,
        F: Fn(T, P)
    {
        (self.f)(self.data, arg);
    }
}

Сначала один разреализован RFC «подразумеваемые границы признаков», это позволяет мне исключить дублированные границы признаков из всех блоков impl.

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

impl<F, T, P> Foo<F, T> 
where
    T: Copy,
    F: Fn(T, P),
{
    fn call_f(&self, arg: P) {
        (self.f)(self.data, arg);
    }
}

Здесь есть небольшая проблема,похож на тот, который вы нашли сами: unconstrained type parameter: P.Однако теперь, когда мы дошли до этого, вы можете решить эту проблему очень просто, введя черту (вы можете назвать ее лучше для вашего конкретного случая использования):

trait FIsAFunction<F, T, P> {
    fn call_f(&self, arg: P);
}

impl<F, T, P> FIsAFunction<F, T, P> for Foo<F, T> 
where
    T: Copy,
    F: Fn(T, P),
{
    fn call_f(&self, arg: P){
        (self.f)(self.data, arg);
    }
}

И пользователям не нужноделать что-то странное [1] :

fn main() {
    fn callback(x: u32, y: &str) {
        println!("I was given {:?} and {:?}", x, y)
    }
    let foo = Foo { data: 1u32, f: callback };
    foo.call_f("hello!");
}

[1] Они могут иметь use черту.Что не так странно: вы уже должны сделать это с большим количеством std вещей, таких как std::io::Read и т. Д.

0 голосов
/ 04 июня 2018

Решение № 2 - единственный известный мне способ заставить эту работу работать с границами структуры.По моему мнению, заставить его работать без границ структуры, как Питер Холл предлагает , обычно предпочтительнее, потому что он ставит границы только там, где они действительно значимы, но если вы находите это обременительным, дополнительный параметр типа является вашей единственной возможностью.

Другая возможность - добавить параметр типа в структуру.Мне уже не нравится это решение, потому что параметр по своей природе не принадлежит структуре.

Второй параметр необходим.Типы аргументов Fn -определяющего типа - это параметры Fn черты , поэтому в принципе вы можете иметь как impl Fn(i32, i32) for X, так и impl Fn(i32, String) for X, так же как вы можете иметь оба impl AsRef<i32> for X и impl AsRef<String> for X.

На самом деле, если вы не смотрите на это слишком усердно, это как бы HRTB уже работают: функция может реализовать Fn(&'x i32) для некоторых конкретных время жизни 'x, или он может реализовать for<'a> Fn(&'a i32), что означает, что существует бесконечное число возможных Fn черт, которые он реализует.

Но вы обнаружили проблему добавления параметра для P: параметр не используется.

Эту проблему можно решить, добавив поле PhantomData<P>, но в этом нет необходимости

Компилятородноранговые узлы внутри структур для определения дисперсии их параметров. В этом случае предположим, что P является ссылочным типом.Безопасно ли передавать Foo<_, &'static T> функции, ожидающей Foo<_, &'a T>?А как же наоборот?

(Как указано в связанном ответе, ограничения - предложения where - не учитываются при определении дисперсии, поэтому PhantomData здесь необходимо.)

Но PhantomData член не должен быть PhantomData<P>, потому что Foo<_, P> не содержит P.Он содержит функцию, которая принимает P в качестве аргумента .Вместо этого вы должны использовать PhantomData<fn(P)>, который сигнализирует компилятору, что дисперсия Foo<F, P> в P совпадает с дисперсией fn(P) - функция (указатель) принимает P.Другими словами, Foo является контравариантным в P.Читателю-человеку это может показаться излишним - в конце концов, у нас уже есть член F, а F должен быть контравариантным в P.Но, ну, в общем, компилятор не настолько умен, чтобы сделать такой вывод, поэтому вы должны изложить его.

(см. раздел Nomicon по подтипу для более точногообъяснение отклонений.)

Что подводит меня к вашему последнему возражению:

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

К сожалению, я не могу придумать решение этой проблемы, кроме "написать хорошую функцию конструктора".Возможно, умный компилятор однажды снимет эту нагрузку, но сейчас PhantomData - это то, что у нас есть.

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