Stream: beginners

Topic: Task {} {} vs Task {} [] vs Task {} *


view this post on Zulip Jared Cone (Sep 29 2024 at 04:34):

What's the difference between Task {} {}, Task {} [] and Task {} *? For the latter two, what would be the rust-equivalent type?

view this post on Zulip Luke Boswell (Sep 29 2024 at 04:46):

Unit Type
Empty List
Anything -- in the return position there is nothing that can anything therefore this Task cannot fail.

view this post on Zulip Luke Boswell (Sep 29 2024 at 04:49):

I think how the function can then used is where the difference is most significant. I'll try and make an example

view this post on Zulip Jared Cone (Sep 29 2024 at 04:52):

Is there any difference between Unit vs Anything? For anything, would the Rust type still be RocResult<(), ()>?

view this post on Zulip Luke Boswell (Sep 29 2024 at 04:54):

For example ... using [] this is concrete and won't unify with the [Exit _ _] type.

app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" }

main : Task {} [Exit I32 Str]
main =

    foo!

    Task.ok {}

foo : Task {} []
foo = Task.ok {}
── TYPE MISMATCH in main.roc ───────────────────────────────────────────────────

Something is off with the body of the main definition:

3│  main : Task {} [Exit I32 Str]
4│  main =
5│
6│      foo!
        ^^^^

This await call produces:

    Task {} []

But the type annotation on main says it should be:

    Task {} [Exit I32 Str]

To fix this we would need to return an open tag union like []_.

view this post on Zulip Luke Boswell (Sep 29 2024 at 04:54):

So foo : Task {} []_ works

view this post on Zulip Luke Boswell (Sep 29 2024 at 04:56):

Jared Cone said:

Is there any difference between Unit vs Anything? For anything, would the Rust type still be RocResult<(), ()>?

You're specifically thinking about the interface between roc and the platform host here... This is currently generated as a RocResult and the * is represented in rust using ()

view this post on Zulip Luke Boswell (Sep 29 2024 at 04:57):

Actually... you can't (shouldn't have) a * being passed to the host

view this post on Zulip Luke Boswell (Sep 29 2024 at 04:57):

That will be a bug because it's size isn't fixed

view this post on Zulip Luke Boswell (Sep 29 2024 at 04:57):

You want a concrete fixed sized type for things you pass across between roc and the host

view this post on Zulip Jared Cone (Sep 29 2024 at 05:01):

Alrighty. Ignoring host interface then, what would be the functional difference in a function that returns a Task {} {} vs. a Task {} *? I was using Task {} {} to indicate a task that can't fail, but then I saw this in the tutorial:

Stdout.line has the type Task {} * because it doesn't produce any values when it finishes (hence the {}) and there aren't any errors that can happen when it runs (hence the *).

view this post on Zulip Jared Cone (Sep 29 2024 at 05:05):

I suppose Task {} {} means a task can fail but there's nothing useful in the error, but how does Task {} * affect callers?

view this post on Zulip Luke Boswell (Sep 29 2024 at 05:07):

It's about how the error type unifies with other things, this affects where and how you use the function.

view this post on Zulip Brendan Hansknecht (Sep 29 2024 at 05:25):

Task {} {}: can succeed or fail but has no other information in either case.

Task {} []: empty tag for the failure case. This means it can not fail. Will unify with any other tag based errors (e.g. Task {} [SomeErr])

Task {} *: unconstrained type variable for the error case. This means it cannot fail. Can unify with anything.

view this post on Zulip Brendan Hansknecht (Sep 29 2024 at 05:27):

Task {} {} and RocResult<(), ()> are equivalent. The other 2 are not actually safe to pass over the host boundary. Their exact implementation will change based on unification.

view this post on Zulip Jared Cone (Sep 29 2024 at 05:33):

Would there be any reason to use Task {} [] instead of Task {} *? Like, any reason a designer would want their task to only unify with other tasks that have tag-based errors?

view this post on Zulip Luke Boswell (Sep 29 2024 at 05:37):

Jared Cone said:

Would there be any reason to use Task {} [] instead of Task {} *? Like, any reason a designer would want their task to only unify with other tasks that have tag-based errors?

I can't think of any reason why you would want to do that. I've used [] in the past because I didn't know the difference.

view this post on Zulip Brendan Hansknecht (Sep 29 2024 at 14:48):

Only reason I can think of is to clarify that you expect a tag union for the error type. Like if all tasks use tag unions for errors and then a few have no error cases, I could see an argument for using [] over *. Especially given I think a number of people find an empty tag easier to understand than *.


Last updated: Jul 06 2025 at 12:14 UTC