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

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

Решение

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

Чтобы схематически представить эту идею в этой статье, давайте представим две разные функции: инкремент и квадратную функцию, которые принимают одни и те же параметры и возвращают один и тот же результат.

type MyParam = u64;
type MyResult = u64;

async fn inc(
    value: MyParam,
) -> MyResult {
    value + 1
}

async fn square(
    value: MyParam,
) -> MyResult {
    value.pow(2)
}

Теперь, когда функции имеют общую черту, мы можем приступить к реализации функции, которая принимает их в качестве параметров.

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

  • FnOnce — это функции, которые можно вызвать один раз
  • FnMut — это функции, которые можно вызывать, если у них есть &mut доступ к своей среде.
  • Fn — это функции, которые можно вызывать, если у них есть только & доступ к своей среде.

Кроме того, из-за асинхронного характера функции, которую мы хотим передать, следует указать, что она возвращает Future . Будущее представляет собой асинхронное вычисление, полученное с использованием async .

Наконец, хотя его можно было встроить, из соображений удобочитаемости я счел удобным использовать ограничения where.

use std::future::Future;

async fn execute<F, Fut>(
    f: F,
    value: MyParam,
) -> MyResult
where
    F: FnOnce(MyParam)…