futures-await icon indicating copy to clipboard operation
futures-await copied to clipboard

Async Main

Open sunjay opened this issue 7 years ago • 8 comments

Hi! Please let me know if this isn't the right place to ask about this or if it has already been discussed. :)

I wrote a library to create animated drawings in Rust. It's specifically designed to be a great tool for teaching Rust. One of the things I'd like to add a few months from now is a whole set of lessons about Rust and async IO. I plan to refactor the library to return futures and then teach the different future combinators using the drawing methods in the library. It should be really cool!

One of the things users of my library will do a lot is write code directly in the main function. For example, to draw a heart, their code might look something like this:

extern crate turtle;

use turtle::Turtle;

fn main() {
    let mut turtle = Turtle::new();

    turtle.set_speed(5);
    turtle.set_pen_size(3.0);
    turtle.set_pen_color("red");

    turtle.pen_up();
    turtle.forward(-50.0);
    turtle.pen_down();

    turtle.set_fill_color("red");
    turtle.begin_fill();
    turtle.left(50.0);
    turtle.forward(111.65);
    turtle.set_speed(7);
    curve(&mut turtle);
    turtle.left(120.0);
    curve(&mut turtle);
    turtle.set_speed(5);
    turtle.forward(111.65);
    turtle.end_fill();

    turtle.forward(20.0);
    for _ in 0..10 {
        turtle.right(2.0);
        turtle.forward(2.0);
    }

    turtle.set_background_color("pink");
}

fn curve(turtle: &mut Turtle) {
    for _ in 0..100 {
        turtle.right(2.0);
        turtle.forward(2.0);
    }
}

Each of these method calls directs a "turtle" (the pen used for drawing) to perform a certain action.

If I were to start to port this to use futures, I would need to move the entire contents of main into a new function, and replace main with a call to wait(). That's a lot of code to suddenly have to move. I personally understand why I need to do that because I know the mechanics behind it, but for a newer Rust user, I think it's important to remove that friction entirely. If they're able to use async/await syntax everywhere else, I think they would naturally try to use it in main too.

This is a very similar issue to the accepted RFC for allowing ? in main. For the same reason that having ? in main is convenient and important, I think having async and await in main from the beginning is really critical too. It would be great if we could have that functionality from the beginning when async/await is added to the language.

// Combines async/await syntax with the `?` in `main` RFC
// Example from: https://github.com/rust-lang/rfcs/blob/master/text/1937-ques-in-main.md#changes-to-doctests
#[async]
fn main() -> Result<(), ErrorT> {
    //...Lots of fancy async code here...
    Ok(())
}

sunjay avatar Nov 10 '17 16:11 sunjay

Also relevant is that C# 7 has a similar async Main feature.

This could be useful to other Rust tools which also need to use await in their main routine.

GabrielMajeri avatar Nov 10 '17 17:11 GabrielMajeri

You do not need to move your code from main. You can put your async code inside an async_block macro.

rushmorem avatar Nov 12 '17 14:11 rushmorem

@rushmorem My question is actually specifically about being able to use #[async] with main. (See the example at the end of the issue description.) I understand that I could wrap main's body in an async_block! macro, but I'd prefer if main supported the syntax that's already usable for all other functions that return something.

My suggestion/proposal goes hand in hand with the already being implemented RFC that allows returning a result from main. :)

sunjay avatar Nov 12 '17 14:11 sunjay

We could perhaps try to allow this! I fear though that we don't have enough of a "default" runtime to know how to run the future returned by main, so we may not have as "easy" of an implementation as other languages perhaps.

alexcrichton avatar Nov 18 '17 08:11 alexcrichton

The way I was imagining it was that code like this:

#[async]
fn foo() -> Result<(), i32> {
    println!("{}", 1 + await!(bar())?);
    Ok(())
}

#[async]
fn bar() -> Result<i32, i32> {
    Ok(2)
}

fn main() -> Result<(), i32> {
    foo().wait()
}

Would become like this:

#[async]
fn main() -> Result<(), i32> {
    println!("{}", 1 + await!(bar())?);
    Ok(())
}

#[async]
fn bar() -> Result<i32, i32> {
    Ok(2)
}

I think that this fits really well with the RFC that allows returning Result from main.

Is there more to that than just calling wait() on the future? I didn't see much else in the examples I looked at in the README. This feature to me was more about adding a convenient syntactic sugar which would make learning easier. It makes sense to just get it to the point where you can return a future that contains a result, then let the implemention of that other RFC handle the result. What do you think?

sunjay avatar Nov 18 '17 14:11 sunjay

@sunjay ah yeah unfortunately wait probably isn't what you want because most applications will want to configure and set up an event loop, which is all sort of "life before main" in this sense unfortunately

alexcrichton avatar Nov 19 '17 16:11 alexcrichton

@alexcrichton Ah yes you're right. Is there any default behaviour we could support at all? I imagine that there must be simpler cases which don't need the entire main loop in order to function. Are there any?

sunjay avatar Nov 19 '17 20:11 sunjay

Well yes my point is we could support something like wait, but it just wouldn't be too useful. I don't think we're at a point yet where there's a "clear and obvious" one-liner to run 90% of async programs.

alexcrichton avatar Nov 20 '17 14:11 alexcrichton