Problemas al utilizar async en Rust - await, función recursiva

Publicado por Brisa
hace 5 meses

Hola,

Estoy desarrollando una aplicación en Rust y me encuentro con un problema al utilizar el operador .await en una función recursiva. Básicamente, necesito hacer una llamada a una función async dentro de esa misma función, pero al intentar ejecutar el programa me arroja el siguiente error:

"error[E0277]: impl Fn<(), Output = std::prelude::v1::LocalWaker> cannot be awaited" "note: &mut is redundant in the pattern &mut _"

Aquí está el código de ejemplo que estoy utilizando:

use std::future::Future;
use std::pin::Pin;
use std::task::{self, Poll};

async fn recursive_function(mut count: u32) {
    println!("Count: {}", count);

    if count > 0 {
        count -= 1;
        recursive_function(count).await; // Error en esta línea
    }
}

fn main() {
    let future = recursive_function(5);
    task::block_on(future);
}

Según entiendo, el error está relacionado con el uso incorrecto del operador .await dentro de una función recursiva. He intentado varias soluciones, como envolver la llamada a la función async en un Box::pin() o utilizando una función helper, pero ninguna de ellas me ha funcionado.

¿Alguien tiene alguna idea de cómo puedo solucionar este problema? Agradecería mucho su ayuda. ¡Saludos!

rust
Respuesta de Gordon Shumway
hace 5 meses

¡Hola Brisa!

El error que estás viendo se debe a cómo se maneja la asincronía en Rust. En Rust, las funciones asíncronas son transformadas por el compilador en implementaciones del trait Future. Para que un Future se ejecute, necesita ser "pinned" y pasado a un executor. Cuando haces llamadas recursivas a async fn, el tamaño de la pila de llamadas no puede ser conocido en tiempo de compilación, lo que lleva al error que estás viendo.

Una manera de resolver esto es eliminando la recursión o cambiándola por iteración. Pero si necesitas mantener la estructura recursiva, puedes probar lo siguiente:

Podrías crear una función asíncrona aparte que efectúe el trabajo y llamarla dentro de tu función recursive_function envolviendo el llamado con Box::pin, que te permitirá tener un tipo de tamaño conocido (el tamaño de un puntero), pero esto es usualmente una señal de que tu diseño podría ser mejorado para evitar la recursión en funciones asíncronas.

Aquí te muestro cómo modificarías tu código para que funcione:

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use futures::executor; // Utiliza la crate futures para el executor

async fn recursive_function(count: u32) -> u32 {
    if count == 0 {
        return count;
    }

    println!("Count: {}", count);
    recursive_next(count - 1).await
}

// Se crea una nueva función auxiliar que encapsula llamadas recursivas
async fn recursive_next(count: u32) -> u32 {
    recursive_function(count).await
}

fn main() {
    let future = recursive_function(5);
    executor::block_on(future); // Usamos el block_on de la crate futures
}

Debes asegurarte de tener la crate futures en tu Cargo.toml para usar su block_on y otras utilidades:

[dependencies]
futures = "0.3"

Ten en cuenta que esta solución sigue siendo un poco un "hack", y normalmente debes tratar de evitar la recursión en código asíncrono en Rust si es posible. Las estructuras de datos y algoritmos iterativos son generalmente más adecuados para la asincronía y pueden evitar muchos dolores de cabeza relacionados con el manejo de Futures y la pila de ejecución.

¡Espero que esta solución te ayude con tu problema! ¡Suerte!