little-dude
little-dude

Reputation: 1674

What is futures::future::lazy for?

In the Tokio documentation we have this snippet:

extern crate tokio;
extern crate futures;

use futures::future::lazy;

tokio::run(lazy(|| {
    for i in 0..4 {
        tokio::spawn(lazy(move || {
            println!("Hello from task {}", i);
            Ok(())
        }));
    }

    Ok(())
}));

The explanation for this is:

The lazy function runs the closure the first time the future is polled. It is used here to ensure that tokio::spawn is called from a task. Without lazy, tokio::spawn would be called from outside the context of a task, which results in an error.

I'm not sure I understand this precisely, despite having some familiarity with Tokio. It seems that these two lazy have slightly different roles, and that this explanation only applies to the first one. Isn't the second call to lazy (inside the for loop) just here to convert the closure into a future?

Upvotes: 2

Views: 1993

Answers (1)

Shepmaster
Shepmaster

Reputation: 431469

The purpose of lazy is covered by the documentation for lazy:

Creates a new future which will eventually be the same as the one created by the closure provided.

The provided closure is only run once the future has a callback scheduled on it, otherwise the callback never runs. Once run, however, this future is the same as the one the closure creates.

Like a plain closure, it's used to prevent code from being eagerly evaluated. In synchronous terms, it's the difference between calling a function and calling the closure that the function returned:

fn sync() -> impl FnOnce() {
    println!("This is run when the function is called");
    
    || println!("This is run when the return value is called")
}

fn main() {
    let a = sync();
    println!("Called the function");
    a();
}

And the parallel for futures 0.1:

use futures::{future, Future}; // 0.1.27

fn not_sync() -> impl Future<Item = (), Error = ()> {
    println!("This is run when the function is called");

    future::lazy(|| {
        println!("This is run when the return value is called");
        Ok(())
    })
}

fn main() {
    let a = not_sync();
    println!("Called the function");
    a.wait().unwrap();
}

With async/await syntax, this function should not be needed anymore:

#![feature(async_await)] // 1.37.0-nightly (2019-06-05)

use futures::executor; // 0.3.0-alpha.16
use std::future::Future;

fn not_sync() -> impl Future<Output = ()> {
    println!("This is run when the function is called");

    async {
        println!("This is run when the return value is called");
    }
}

fn main() {
    let a = not_sync();
    println!("Called the function");
    executor::block_on(a);
}

As you've identified, Tokio's examples use lazy to:

  • ensure that the code in the closure is only run from inside the executor.
  • ensure that a closure is run as a future

I view these two aspects of lazy as effectively the same.

See also:

Upvotes: 6

Related Questions