Skip to content
This repository has been archived by the owner on Mar 3, 2020. It is now read-only.

Async Main #42

Open
sunjay opened this issue Nov 10, 2017 · 8 comments
Open

Async Main #42

sunjay opened this issue Nov 10, 2017 · 8 comments

Comments

@sunjay
Copy link

sunjay commented Nov 10, 2017

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(())
}
@GabrielMajeri
Copy link

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.

@rushmorem
Copy link

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

@sunjay
Copy link
Author

sunjay commented Nov 12, 2017

@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. :)

@alexcrichton
Copy link
Owner

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.

@sunjay
Copy link
Author

sunjay commented Nov 18, 2017

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?

@alexcrichton
Copy link
Owner

@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

@sunjay
Copy link
Author

sunjay commented Nov 19, 2017

@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?

@alexcrichton
Copy link
Owner

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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants