Пытаемся понять 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, отключив пожизненное исключение.
- Мы не можем поместить время жизни в определение «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
За пределами тяжелого подъема, на который они указывали, «для ' утверждение. Я видел это в своих ошибках итератора, но не понял. Похоже, это тема для следующего исследования.