2
votes

Here is an artificial toy example that demonstrates my problem:

def sscce(): Int = {
  val rand = new Random()
  var count = 0
  while (true) {   // type mismatch; found: Unit, required: Int
    count += 1
    if (rand.nextInt() == 42) return count
  }
}

How can I help the compiler understand that this method will always return an Int?

I know the above toy example could easily be refactored to get rid of the infinite loop altogether, but I really want to have the infinite loop in my actual code. Trust me on this ;)

5
Your code doesn't, technically speaking, return an Int. It returns an Int if the function returns at all. I suspect this distinction is the problem. What are you trying to solve? You say you really want an infinite loop, but perhaps your real world problem can have another solution.Daenyth
I really love infinite loops, but they take forever to finish.Michael Zajac
You can't prove that it will always be executed because it won't always be executed. It will usually be executed, but scala doesn't provide a Usually[Int] type. I'd be shocked if there wasn't a better way to do this instead of using the infinite loop.Daenyth

5 Answers

4
votes

You can also do:

def foo: Int = {
  ...
  while(true) {
    ... return ...
  }
  throw new IllegalStateException  // unreachable
}

this will typecheck because the type of the throw is Nothing, which is a subtype of Int.

6
votes

Always return an Int:

def sscce(): Int = {
  val rand = new Random()
  var count = 0
  while (true) {
    count += 1
    if (rand.nextInt() == 42) return count
  }
  count // <-- this
}
2
votes

See this question. While loops don't return a value. i.e. they return Unit which is the last statement in your function. So, the definition says it returns an Int but it actually returns Unit thus the type error. @ionut's answer fixes the type error by returning count as the last statement or here is a recursive approach.

def sscce(): Int = {
  val rand = new Random()
  def ssccer(count: Int): Int = {
    if(rand.nextInt == 42) return count
    else ssccer(count + 1)
  }
  ssccer(0)
}
2
votes

Per the SLS, a while loop is executed similarly to:

def whileLoop(cond: => Boolean)(body: => Unit): Unit  =
  if (cond) { body ; whileLoop(cond)(body) } else {}

ie., it returns Unit. So the compiler sees the while as the last statement in sscce(), and therefore assumes that you're trying to return Unit. I don't think it's smart enough to realize that return count will eventually always return an Int.

The simple solution is to follow the suggestion of @Brian or @IonutGStan, and force it to return count, whether it truly needs it or not.

2
votes

From a code quality standpoint, it would be good to ditch the while(true) loop and replace it with something more readable. As a nice side effect, it also solves your problem:

def sscce(): Int = {
  val rand = new Random()
  var count = 1
  while (rand.nextInt() != 42) {
    count += 1
  }
  count
}