Skip to content

boundary/break leaks implementation details and may cause unexpected behaviour #17737

Closed as not planned
@szymon-rd

Description

@szymon-rd

Compiler version

3.3.0

Minimized code

A simple example of a code that contains behavior unexpected by the user, caused by implementation detail of boundary/break:

//> using scala 3.3
import java.net.http.HttpTimeoutException
import scala.util.boundary
import boundary.break

object Main extends App:
  boundary:
    while true do
      try
        makeRequest()
        break()
      catch
        case _: Exception => //just retry
  println("Finish")

  var c = 0
  def makeRequest() =
    println("Making request")
    if c < 3 then
      c = c + 1
      throw new HttpTimeoutException("timeout")

This loop would never finish, as it would first catch the real timeout exception and then all the Break exceptions. It makes perfect sense it works that way, given how the boundary/break is implemented. However, catching all the Exceptions on retries when, e.g., waiting for a service to start responding, is a widespread pattern. Scala users writing simple programs like this must be aware of this implementation detail.

What's more - that's a basic example. Along the way, library maintainers will use the boundary/break in their libraries. Given an additional layer over this mechanism, the leaking implementation detail will be even more misleading.

To solve the antipattern of catching Exception, we offer the NonFatal with unapply. However, Break will be caught as NonFatal by the users, so it won't help as well. It may even cause more confusion, as the previous implementation detail of breaking (the ControlThrowable) was not matched by NonFatal and was sometimes used as a solution.

Proposal

After talking about it, we propose that the compiler could report an error if there is a Label in the implicit scope and the Break is caught indirectly (i.e. by matching Exception). It would prevent the users from experiencing the leakage of implementation detail and the possible confusion when their code does not work as expected. Nevertheless, I would like to hear some feedback on that.

CC @odersky @bishabosha @sjrd

Update: Problem with the solution we proposed is that it would not solve the problem of runtime matching in unapplies (so, for example, NonFatal)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions