question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Confusing semantics of uninitialized `final val` containing literal constants

See original GitHub issue

Compiler version

3.0.0

Minimized code

class Creature {
  val range: Int = 10
  println(range)
}

class Ant1 extends Creature {
  override final val range = 2
}

/*final*/ class Ant2 extends Creature {
  override /*final*/ val range: Int = 2
}

object Test extends App {
  new Ant1
  new Ant2
}

Output

➜  snips scala question.scala
2
0

If Ant2.range is final, it is inlined (questionably):

➜  snips scala question.scala
2
2

If class Ant2 is final, non-final range is not inlined.

Expectation

It works as described in the one-question FAQ

Asked on gitter in Scala 2 context.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:25 (17 by maintainers)

github_iconTop GitHub Comments

3reactions
liufengyuncommented, May 30, 2021

Compile with -Ysafe-init, we get:

sbt:scala3> scalac -d out -Ysafe-init examples/i12570.scala

-- Warning: examples/i12570.scala:7:21 -----------------------------------------
7 |  override final val range = 2
  |                     ^
  |                     Access non-initialized value range. Calling trace:
  |                      -> class Ant1 extends Creature {	[ i12570.scala:6 ]
  |                       -> val range: Int = 10	[ i12570.scala:2 ]
  |                        -> println(range)	[ i12570.scala:3 ]
-- Warning: examples/i12570.scala:11:25 ----------------------------------------
11 |  override /*final*/ val range: Int = 2
   |                         ^
   |         Access non-initialized value range. Calling trace:
   |          -> /*final*/ class Ant2 extends Creature {	[ i12570.scala:10 ]
   |           -> val range: Int = 10	[ i12570.scala:2 ]
   |            -> println(range)	[ i12570.scala:3 ]
2 warnings found

The checker currently does not optimize for constant folded fields. It seems to be a good choice to defend against subtle code that employs constant folding and “violates” initialization semantics.

2reactions
sjrdcommented, Jun 9, 2021

I believe we can reconcile all these apparently conflicting requirements using the following reasoning:

  • Accessing a val before it has been defined, in the initialization order, is unspecified behavior. It may return any value inhabiting the val’s type (or an invalid null with the explicit nulls).
  • The initialization checker reports any such use as invalid, since the behavior of the program would be unspecified in that case.
  • Knowing that, the compiler is free to optimize vals with literals as defs, because it does not change the semantics of any program that stays within specified behavior. It may change the effective behavior of a program that triggers unspecified behavior, but it has the right to do so because, well, it’s unspecified.

That means that:

  • The init checker should not be changed; it is correct to flag any use-before-initialization as unspecified behavior.
  • The compiler can optimize final vals and vals in final classes.
Read more comments on GitHub >

github_iconTop Results From Across the Web

when exactly are we supposed to use "public static final String"?
I think the answer is valid, but maybe confusing some terms - e.g. I think with "constant values" they mean "literal", and indeed...
Read more >
Constant expressions - cppreference.com
A literal constant expression of array or class type requires that each subobject is initialized with a constant expression.
Read more >
4.13 — Const variables and symbolic constants - Learn C++
In programming, a constant is a value that may not be changed. C++ supports several types of constants: const variables (which we'll cover ......
Read more >
Effective Go - The Go Programming Language
As a limiting case, if a composite literal contains no fields at all, it creates a zero value for the type. The expressions...
Read more >
Warning Options (Using the GNU Compiler Collection (GCC))
This is the warning level that is enabled by -Wunused-variable for C. It warns only about unused static const variables defined in the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found