Пытаемся понять Fn в Rust

(!) Предупреждение. Если вы новичок в Rust, не доверяйте всему, что написано в этом посте. Этот пост может содержать правдоподобную чушь.

Я пытаюсь познакомиться с функциями Rust как с объектами первого класса.

Прежде чем перейти к Rust, я резюмирую то, что знаю о «функциях первоклассных граждан» в Python.

def x():
  return True
def y():
  return x
def y2():
  return lambda : true
class X:
  def __call__(self):
    return True

По сути, это все. Мы можем добавить еще несколько лямбд, чтобы получить частичное применение, и декораторы, чтобы крутить мозги, но в основном это все. Так же просто, как сказать «Я возвращаю функцию x» и все.

Теперь о Rust.

С функцией «x» проблем нет:

fn x() -> bool{
    true
}

Но функция «y» стала ... сложной.

Это не компилируется:

fn y() -> Fn()->bool{
    x
}

Ошибка очень многословная и очень интригующая:

error[E0308]: mismatched types
 --> src/main.rs:6:5
  |
5 | fn y() -> Fn()->bool{
  |           ---------- expected `(dyn std::ops::Fn() -> bool + 'static)` because of return type
6 |     x
  |     ^ expected trait std::ops::Fn, found fn item
  |
  = note: expected type `(dyn std::ops::Fn() -> bool + 'static)`
             found type `fn() -> bool {x}`
error[E0277]: the size for values of type `(dyn std::ops::Fn() -> bool + 'static)` cannot be known at compilation time
 --> src/main.rs:5:11
  |
5 | fn y() -> Fn()->bool{
  |           ^^^^^^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `(dyn std::ops::Fn() -> bool + 'static)`
  = note: to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
  = note: the return type of a function must have a statically known size

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

  1. Мы не можем поместить время жизни в определение «x», поскольку здесь нет ссылок. Мы возвращаем объект по значению, используя черту Copy логического…

Подождите, прежде чем вдаваться в подробности, давайте упростим наш пример:

fn x() {
}
fn y() -> Fn(){
    x
}

Больше никаких надоедливых аргументов. Ошибка та же, плюс / минус аргументы.

Ключевой момент вот в чем:

= note: expected type `(dyn std::ops::Fn() + 'static)`
             found type `fn() {x}`

… Подождите минуту. Rust ожидал типа fn, и я объявил Fn,, что приводит нас к внутреннему устройству некоторых стандартных библиотек.

Давайте изменим код:

fn x() {
    println!("Called x");
}
fn y() -> fn(){
    println!("Called y");
    x
}
fn main() {
    y()();
}

Ура! Компилируется и работает! Давайте отремонтируем наши bool вещи.

fn x(input: bool) -> bool {
    println!("Called x with {}", input);
    ! input
}
fn y() -> fn(bool)->bool{
    println!("Called y");
    x
}
fn main() {
    y()(true);
    y()(false);
}

Работает как положено:

Called y
Called x with true
Called y
Called x with false

Я очень, очень рад, что это была глупая опечатка. Моя основная интуиция в отношении Rust была правильной.

Дополнительное вложение работает как положено, это тривиально. Вот подпись "z":

fn z() -> fn() -> fn(bool) -> bool

Перед тем как перейти к закрытию, еще один тест: возврат вложенной функции. Хорошо .. Это сработало, как и ожидалось:

fn z() -> fn() -> fn(bool) -> bool{
    fn y() -> fn(bool) -> bool{
        fn x(input: bool) -> bool {
            println!("Called x with {}", input);
            ! input
        }
        println!("Called y");
        x
    }
    println!("Called z");
    y
}
fn main() {
    z()()(true);
    z()()(false);
}

Насколько мне известно, Rust не поддерживает закрытие функций: нельзя использовать переменные вне исходной функции.

Этот код не работает:

fn z(input: bool) -> fn() -> fn() -> bool{
    let save = input;
    fn y() -> fn()->bool{
        fn x() -> bool {
            println!("Called x");
            save
        }
        println!("Called y");
        x
    }
    println!("Called z");
    y
}
fn main() {
    z(true)()();
    z(false)()();
}

Потому что

error[E0434]: can't capture dynamic environment in a fn item
 --> src/main.rs:7:13
  |
7 |             save
  |             ^^^^
  |
  = help: use the `|| { ... }` closure form instead

Довольно просто.

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

fn z() -> fn() -> fn(bool) -> bool{
    fn y() -> fn(bool)->bool{
        |x|{ println!("lamda!"); !x}
    }
    println!("Called z");
    y
}
fn main() {
    z()()(true);
    z()()(false);
}

Да, все работает как положено. Итак, это все, что касается детских вещей. Теперь перейдем к основной теме: чертам Fn / Mut / Once.

Im черта

Опять же, я начну с простой вещи, которая работает:

fn y(state: bool) -> impl Fn(bool)-> bool {
    move |x|{
        println!("state, {}, value: {}", state, x);
        !x
    }
}
fn main() {
    y(true)(true);
    y(false)(false);
}

Теперь вот что замешательство. Я хочу создать закрытие, которое возвращает закрытие.

Код выглядит невинно:

fn y(state: bool) -> impl Fn()-> impl Fn(bool)->bool {
    move ||{
        move |x:bool|{
            println!("state, {}, value: {}", state, x);
            !x
        }
    }
}

Но его нельзя скомпилировать. Ни сообщение об ошибке, ни rustc --explain не дают никаких подсказок:

error[E0562]: `impl Trait` not allowed outside of function and inherent method return types
 --> src/main.rs:2:34
  |
2 | fn y(state: bool) -> impl Fn()-> impl Fn(bool)->bool {
  |                                  ^^^^^^^^^^^^^^^^^^^

Небольшой шаг назад: можем ли мы вернуть функцию из замыкания?

fn x(inp: bool) -> bool{
    !inp
}
fn y(state: bool) -> impl Fn()-> fn(bool)->bool {
    move ||{
        println!("state, {}", state);
        x
    }
}
fn main() {
    y(true)()(true);
    y(false)()(false);
}

Да мы можем.

Я попросил помощи в предыдущем случае. К сожалению, мы не можем делать то, что я хочу. Вот мой полный вопрос с ответом. Проблема заключается в сигнатуре функции, где impl разрешен только для функций. И мне сложно это сформулировать, но я пытаюсь:

В подписи fn y(state: bool) -> impl Fn()-> impl Fn(bool)->bool мы говорим, что «y» реализует черту «Fn->imp Fn(bool) ->bool». Это не признак «Fn», это все аргументы Fn и возвращаемые значения. А в текущем состоянии Rust нам не разрешено использовать «impl» внутри объявления трейта.

Предлагаемое решение заключалось в переходе от анонимного закрытия к правильным структурам, которые могут удерживать состояние менее автоматическим способом. Может быть это. Тем не менее, я чувствую некоторую грубость в Rust в отношении этих вещей. В Python они выглядят тривиально, но в Rust иногда вызывают головокружение, когда они говорят «нет».

В своих чтениях по этой теме я нашел запрещенный текст из запрещенной книги: https://doc.rust-lang.org/nomicon/hrtb.html
За пределами тяжелого подъема, на который они указывали, «для ' утверждение. Я видел это в своих ошибках итератора, но не понял. Похоже, это тема для следующего исследования.