Lgd. Viktor Klang

Systems all the way down

Viktor Klang bio photo


This is the sixth of several posts describing the evolution of scala.concurrent.Future in Scala 2.12.x. For the previous post, click here.

Missing utilities: unit & never

Something I’ve always felt was missing is having a «zero» for Future—or more frequently called a «default instance» of a Future.

What’s nice about having this is that, technically, Future.apply[T](logic: => T)(implicit ec: ExecutionContext) could be viewed as, and implemented like:

def apply[T](logic: => T)(implicit ec: ExecutionContext): Future[T] =
  unit.map(_ => logic)

Q: Where is it useful? A: Anywhere you have Future.successful(())

Example:

//Imagine we don't want to try to store nulls or empty strings
//Returns a Future[Unit] which will be completed once the operation has completed
def storeInDB(s: String): Future[Unit] = s match {
  case null | "" => Future.unit
  case other => db.store(s)
}

val f: Future[String] = 
val f2 = f flatMap storeInDB

Another important scenario, which wasn’t really readily solvable unless you were comfortable with implementing it yourself was a Future which would never complete.

Now, the naïve implemention of said Future would look something like:

val neverCompletingFuture: Future[Nothing] = Promise[Nothing].future

Take a few seconds to think about the following question: In what ways would that solution be undesirable?

Hint: What happens with logic which gets added to neverCompletingFuture? Example:

val fooFuture = neverCompletingFuture.map(_ => "foo")

//or

neverCompletingFuture.onComplete(println)

Well, what happens is that the logic is packaged and added to the neverCompletingFuture instance in order to be executed when neverCompletingFuture completes (which is never), and now we have a hard to spot invisible memory leak!

So, in order to support the case when you want to be able to represent a Future which never completes, but also doesn’t leak memory when used as a plain Future, use Future.never.

An example use-case might be when you need to pass in a Future which should not affect the outcome, as in:

val someImportantFuture: Future[String] = 
val someLessImportantFuture: Future[String] =
    if (someCondition) Future.never else Future.successful("pigdog")

// Will always pick someImportantFuture if someCondition is true
val first = Future.firstCompletedOf(someImportantFuture, someLessImportantFuture)

Benefits:

  1. Future.unit is a «zero» instance which is cached
  2. Future.never removes the risk of memory leaks when used as a Future instance which never completes

Click here for the next part in this blog series.

Cheers,