Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The CompletableDeferred having strange constructors #3134

Open
tekener opened this issue Jan 12, 2022 · 3 comments
Open

The CompletableDeferred having strange constructors #3134

tekener opened this issue Jan 12, 2022 · 3 comments

Comments

@tekener
Copy link

tekener commented Jan 12, 2022

We spend hours last week to find a bug introduced by code like this:

fun getValueAsync(someCondition: Boolean): Deferred<Int?> = runBlocking {
  if(someCondition){
   CompletableDeferred(5)
  } else {
   CompletableDeferred(null)
 }
}

What i thought i am doing is returning a Deferred resolving to null.
But there are two constructors, one with T and one with parent: Job?. If you call it like CompletableDeferred(5) it resolved to 5, if you call it like CompletableDeferred(null) it is never resolving because the null resulting in calling the constructor with the parent Job.

I would suggest to get rid of the constructors and provide some static methods with meaningful names instead (like in scala CompletableDeferred.successful(null)

public fun <T> CompletableDeferred(parent: Job? = null): CompletableDeferred<T> = CompletableDeferredImpl(parent)

public fun <T> CompletableDeferred(value: T): CompletableDeferred<T> = CompletableDeferredImpl<T>(null).apply { complete(value) }

@qwwdfsad
Copy link
Collaborator

qwwdfsad commented Jan 12, 2022

As a side note, the first overload wins because it's more specific: any argument supplied to the first overload can also be supplied to the second, which makes the first one more specific. Inferred via return type generic argument is not used during such substitution check.

The problem is indeed a bummer and we either can do something with a less popular overload (Job?) or introduce an IDE inspection for that (e.g. "consider using an explicit parameter name")

@merkinth
Copy link

merkinth commented Jul 5, 2023

As a workaround you could cast null to the expected type, like CompletableDeferred(null as? Int)

@elizarov
Copy link
Contributor

elizarov commented Jul 6, 2023

Yes, the overload of the CompletableDeferred constructor is an unfortunate historical accident. If we have an alternative API for creating parent/child hierarchies in the future, we should definitely consider deprecating and hiding the constructor with Job?.

Another road is, indeed, creating explicitly named "successful/completed" factory functions. Also, we can declare yet another overload specifically for CompletableDeferred(null) pattern with the following signature:

fun <T> CompletableDeferred(value: Nothing?): CompletableDeferred<T?>

It can also be marked as deprecated to force anyone who wrote CompletableDefferred(null) into thinking mode at what exactly they wanted to achieve.

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

No branches or pull requests

4 participants