Re: Static checking for dropped futures

66 views
Skip to first unread message

Brian Wilkerson

unread,
Sep 3, 2015, 5:58:07 PM9/3/15
to General Dart Discussion
There are two ways that I can think of for handling false positives. One is to carefully define the hint such that there won't be any false positives. The other is to specify a way to tell the analyzer that you know what you're doing. The first is almost always better.

For the case of dropped futures, I can't think of a way to avoid false positives. As far as I know the cases where it's ok to not wait for the future look (statically) just like the case where it isn't. (If that's not true, I'd love to hear what the rules should be for deciding when to produce a hint.) That leaves me thinking we need some way to tell the analyzer when it's ok.

We can do that by extending the language (something like a "don't wait for" keyword where the "await" keyword would have been) or by using annotations. I really don't want a separate keyword, and I doubt anyone else does either :-).

The problem with annotations, though, is their granularity. It seems plausible that users could have a single method that invokes multiple future-returning functions/methods and that only one of those invocations should be allowed to be dropped. Unfortunately, as things stand today, you can't annotate expressions. You can't even annotate statements, though that would probably be sufficient because I expect that the invocation would almost always be the top-level expression in an expression statement.

So, is there a better solution that I'm not thinking of?

Brian

Paul Berry

unread,
Sep 3, 2015, 6:13:54 PM9/3/15
to General Dart Discussion
Two other ideas for suppressing the warning:

1. Assign the future to a local variable whose name is "_".  The future isn't dropped since we assign it to a variable.  And the "unused variable" hint is not a problem because we already suppress that hint when the name is "_".  Disadvantage: you can only have one local called "_" per method.

2. Create a do-nothing function you can pass the future to when you really mean to ignore it:

void ignoreFuture(Future _) {}

This works to suppress the warning because the analyzer only analyzes one function at a time, so at the call site it has no way of knowing that ignoreFuture() will drop the future.  And the declaration of ignoreFuture is also warning-free because the variable's name is "_".  (Also apparently we don't give the "unused variable" warning for parameters.  Who knew?)  Incidentally, we could always add a definition of "ignoreFuture" to dart:async as a convenience to users.

Both of these approaches have zero run-time overhead because they are easily optimized away by the VM (and presumably also dart2js).

Paul

--
For other discussions, see https://groups.google.com/a/dartlang.org/
 
For HOWTO questions, visit http://stackoverflow.com/tags/dart
 
To file a bug report or feature request, go to http://www.dartbug.com/new

To unsubscribe from this group and stop receiving emails from it, send an email to misc+uns...@dartlang.org.

John Messerly

unread,
Sep 3, 2015, 6:19:50 PM9/3/15
to General Dart Discussion
Just curious, is the context for this discussion https://github.com/dart-lang/sdk/issues/24171?

(FWIW, I can't think of a reason not to wait on a Future result. That means "I'm kicking off this async operation and I don't care when/if it ever completes". Even in tests/prototypes, it doesn't hurt to add "await" and ensure predictability.)

Brian Slesinsky

unread,
Sep 3, 2015, 6:20:56 PM9/3/15
to mi...@dartlang.org
I was just going to suggest '_' myself:

var _ = await something();

Here, '_' is a special variable name that indicates a deliberately unused variable.

I vaguely recall that there's a language where '_' is treated specially as an ignored value in a pattern match. I imagine that in Dart that could that being useful for an event handler where you're deliberately ignore some parameters. So, you should be able to use '_' more than once as a parameter name for unused parameters in a function, and it shouldn't complain.

But even without extra-special treatment, it's still a useful convention.

Brian Wilkerson

unread,
Sep 3, 2015, 6:21:52 PM9/3/15
to General Dart Discussion
John,

Just curious, is the context for this discussion https://github.com/dart-lang/sdk/issues/24171?

Yes, and no. I actually started it in response to the DEP committee meeting notes, but the issue was created before the meeting and then the same topic was discussed.

Brian

Brian Slesinsky

unread,
Sep 3, 2015, 6:25:30 PM9/3/15
to mi...@dartlang.org
Fire-and-forget is useful when the notification happens in a different way. (For example, one function fires off some tasks and a different function handles completion via some internal mechanism rather than the future.)

Bob Nystrom

unread,
Sep 3, 2015, 6:30:42 PM9/3/15
to General Dart Discussion

On Thu, Sep 3, 2015 at 3:19 PM, 'John Messerly' via Dart Misc <mi...@dartlang.org> wrote:
(FWIW, I can't think of a reason not to wait on a Future result. That means "I'm kicking off this async operation and I don't care when/if it ever completes". Even in tests/prototypes, it doesn't hurt to add "await" and ensure predictability.)

One example is main(). I think event handlers are another case where awaiting may not add value.

In rare cases, if you're building your own asynchrony primitives (what can I say? It happens.) you may know that the future's completion is tracked in some other way already.

I like Paul's suggestions of "_" or a ignore() function. We'd probably want to special-case main() too.

On Thu, Sep 3, 2015 at 3:13 PM, 'Paul Berry' via Dart Misc <mi...@dartlang.org> wrote:
(Also apparently we don't give the "unused variable" warning for parameters.  Who knew?)

That way you can override a method or a pass a callback to something that passes it parameters even if you don't actually need to use them. :)

Cheers!

- bob
 


John Messerly

unread,
Sep 3, 2015, 6:39:29 PM9/3/15
to General Dart Discussion
On Thu, Sep 3, 2015 at 3:30 PM, 'Bob Nystrom' via Dart Misc <mi...@dartlang.org> wrote:

On Thu, Sep 3, 2015 at 3:19 PM, 'John Messerly' via Dart Misc <mi...@dartlang.org> wrote:
(FWIW, I can't think of a reason not to wait on a Future result. That means "I'm kicking off this async operation and I don't care when/if it ever completes". Even in tests/prototypes, it doesn't hurt to add "await" and ensure predictability.)

One example is main(). I think event handlers are another case where awaiting may not add value.

So for main, that's because there's code in the runtime that will wait for all pending operations. It's sort of like the system is adding a big "await" at the end of your main.

Not sure I follow the event handler point. 

In rare cases, if you're building your own asynchrony primitives (what can I say? It happens.) you may know that the future's completion is tracked in some other way already.

I guess in that case, you may not be using "async" in the first place? 

I like Paul's suggestions of "_" or a ignore() function. We'd probably want to special-case main() too.

seems reasonable to me
 

On Thu, Sep 3, 2015 at 3:13 PM, 'Paul Berry' via Dart Misc <mi...@dartlang.org> wrote:
(Also apparently we don't give the "unused variable" warning for parameters.  Who knew?)

That way you can override a method or a pass a callback to something that passes it parameters even if you don't actually need to use them. :)

Cheers!

- bob
 


Lasse R.H. Nielsen

unread,
Sep 4, 2015, 2:14:58 AM9/4/15
to mi...@dartlang.org
How about ignoring dropped futures in any non-async function?
That is, only give hints if a future is dropped inside an async or async* function where awaiting it would be trivial.

That would automatically handle the case of a non-async "main" function starting an otherwise async program.

/L

--
Lasse R.H. Nielsen - l...@google.com  
'Faith without judgement merely degrades the spirit divine'
Google Denmark ApS - Frederiksborggade 20B, 1 sal - 1360 København K - Denmark - CVR nr. 28 86 69 84

Günter Zöchbauer

unread,
Sep 4, 2015, 2:47:57 AM9/4/15
to Dart Misc


On Friday, September 4, 2015 at 8:14:58 AM UTC+2, Lasse Reichstein Holst Nielsen wrote:
How about ignoring dropped futures in any non-async function?
That is, only give hints if a future is dropped inside an async or async* function where awaiting it would be trivial.


I assumed this topic is for async/async* functions only anyway.
A way to silence the hint in async/async* functions is still necessary.
Assigning to _ would be fine for me.

What about inline calls like 
print(someAsyncCall());

In this case a hint would be great 
but when I want to pass the future I would probably get a hint
funcExpectsAFuture(someAsyncCall());

How could the analyzer distinguish between these two. funcExpectsAFuture could look like
Future funcExpectsAFuture(var arg) {...}

Günter Zöchbauer

unread,
Sep 4, 2015, 2:52:59 AM9/4/15
to Dart Misc


On Friday, September 4, 2015 at 12:13:54 AM UTC+2, Paul Berry wrote:
Two other ideas for suppressing the warning:

1. Assign the future to a local variable whose name is "_".  The future isn't dropped since we assign it to a variable.  And the "unused variable" hint is not a problem because we already suppress that hint when the name is "_".  Disadvantage: you can only have one local called "_" per method.


Assigning to a local variable "_" more than once shouldn't cause any problems, should it?

Günter Zöchbauer

unread,
Sep 4, 2015, 3:01:15 AM9/4/15
to Dart Misc


On Friday, September 4, 2015 at 12:19:50 AM UTC+2, John Messerly wrote:
Just curious, is the context for this discussion https://github.com/dart-lang/sdk/issues/24171?

(FWIW, I can't think of a reason not to wait on a Future result. That means "I'm kicking off this async operation and I don't care when/if it ever completes". Even in tests/prototypes, it doesn't hurt to add "await" and ensure predictability.)



I commented on the issue already.

It is not about "I'm kicking off this async operation and I don't care when/if it ever completes" 
its about "I'm kicking off this async operation and care when it completes, but until then I'm doing something else, instead of just waiting" 
 
await callAsyncA();
await callAsyncB();
callAsyncC().then((e) => doSomething, onError: (e) => handleError);
await callAsyncD();

I can also add a timeout to the future to ensure it completes eventually.

Günter Zöchbauer

unread,
Sep 4, 2015, 3:18:09 AM9/4/15
to Dart Misc


On Friday, September 4, 2015 at 12:13:54 AM UTC+2, Paul Berry wrote:
Two other ideas for suppressing the warning:

1. Assign the future to a local variable whose name is "_".  The future isn't dropped since we assign it to a variable.  And the "unused variable" hint is not a problem because we already suppress that hint when the name is "_".  Disadvantage: you can only have one local called "_" per method.

2. Create a do-nothing function you can pass the future to when you really mean to ignore it:

void ignoreFuture(Future _) {}


print(asyncCall());

I would want a hint here. I probably don't want to print the Future.
This is even more important than at the beginning of a statement where a missing `await` is easier spotted.

Bob Nystrom

unread,
Sep 4, 2015, 12:26:56 PM9/4/15
to General Dart Discussion
On Thu, Sep 3, 2015 at 3:38 PM, 'John Messerly' via Dart Misc <mi...@dartlang.org> wrote:
One example is main(). I think event handlers are another case where awaiting may not add value.

So for main, that's because there's code in the runtime that will wait for all pending operations. It's sort of like the system is adding a big "await" at the end of your main.

Right.
 
Not sure I follow the event handler point. 

This is what I've heard but I don't have much first-hand experience here. But something like a DOM event handler is sort of like a mini-main(). If you kick off some asynchronous action in response to, say, a mouse down, you don't really need the event handler itself to wait for completion. (It can't anyway, since the underlying machinery calling your event handler doesn't check to see if you returned a future.)


In rare cases, if you're building your own asynchrony primitives (what can I say? It happens.) you may know that the future's completion is tracked in some other way already.

I guess in that case, you may not be using "async" in the first place? 

Ah, good point!

On Thu, Sep 3, 2015 at 11:14 PM, 'Lasse R.H. Nielsen' via Dart Misc <mi...@dartlang.org> wrote:
How about ignoring dropped futures in any non-async function?
That is, only give hints if a future is dropped inside an async or async* function where awaiting it would be trivial.

That would automatically handle the case of a non-async "main" function starting an otherwise async program.

Brilliant. This gives us a much better backwards compatibility story too. Since most older code isn't using async, false positives won't appear there.

Cheers!

- bob


Bob Nystrom

unread,
Sep 4, 2015, 12:28:37 PM9/4/15
to General Dart Discussion

On Fri, Sep 4, 2015 at 12:01 AM, Günter Zöchbauer <gzo...@gmail.com> wrote:

I commented on the issue already.

It is not about "I'm kicking off this async operation and I don't care when/if it ever completes" 
its about "I'm kicking off this async operation and care when it completes, but until then I'm doing something else, instead of just waiting" 
 
await callAsyncA();
await callAsyncB();
callAsyncC().then((e) => doSomething, onError: (e) => handleError);
await callAsyncD();

I can also add a timeout to the future to ensure it completes eventually.

This example would be fine. Since the future returned by callAsyncC() is used (its .then() is invoked), there should be no hint here.

The only case where you'd get a hint is when a future is consigned to the void—when the Future result of an expression in statement position is discarded.

- bob

Paul Berry

unread,
Sep 4, 2015, 12:36:39 PM9/4/15
to mi...@dartlang.org
On Fri, 4 Sep 2015 at 09:28 'Bob Nystrom' via Dart Misc <mi...@dartlang.org> wrote:

On Fri, Sep 4, 2015 at 12:01 AM, Günter Zöchbauer <gzo...@gmail.com> wrote:

I commented on the issue already.

It is not about "I'm kicking off this async operation and I don't care when/if it ever completes" 
its about "I'm kicking off this async operation and care when it completes, but until then I'm doing something else, instead of just waiting" 
 
await callAsyncA();
await callAsyncB();
callAsyncC().then((e) => doSomething, onError: (e) => handleError);
await callAsyncD();

I can also add a timeout to the future to ensure it completes eventually.

This example would be fine. Since the future returned by callAsyncC() is used (its .then() is invoked), there should be no hint here.

Except that then() also returns a future, and *that* future is getting ignored.
 

The only case where you'd get a hint is when a future is consigned to the void—when the Future result of an expression in statement position is discarded.

- bob

Bob Nystrom

unread,
Sep 4, 2015, 12:53:58 PM9/4/15
to General Dart Discussion

On Fri, Sep 4, 2015 at 9:36 AM, 'Paul Berry' via Dart Misc <mi...@dartlang.org> wrote:
Except that then() also returns a future, and *that* future is getting ignored.

Crap.

<shakes first angrily at the sky>

- bob

Günter Zöchbauer

unread,
Sep 4, 2015, 12:54:43 PM9/4/15
to Dart Misc
the future returned by `callAsyncC()` is used but the future returned by `then` or explained differently, the future returned by the entire statement, is still consigned to the void.
In one case I might want to wait for its completion, in another I don't care.
 
- bob

Reply all
Reply to author
Forward
0 new messages