When to use val or def in Scala traits?
Solution 1
A def
can be implemented by either of a def
, a val
, a lazy val
or an object
. So it's the most abstract form of defining a member. Since traits are usually abstract interfaces, saying you want a val
is saying how the implementation should do. If you ask for a val
, an implementing class cannot use a def
.
A val
is needed only if you need a stable identifier, e.g. for a path-dependent type. That's something you usually don't need.
Compare:
trait Foo { def bar: Int }
object F1 extends Foo { def bar = util.Random.nextInt(33) } // ok
class F2(val bar: Int) extends Foo // ok
object F3 extends Foo {
lazy val bar = { // ok
Thread.sleep(5000) // really heavy number crunching
42
}
}
If you had
trait Foo { val bar: Int }
you wouldn't be able to define F1
or F3
.
Ok, and to confuse you and answer @om-nom-nom—using abstract val
s can cause initialisation problems:
trait Foo {
val bar: Int
val schoko = bar + bar
}
object Fail extends Foo {
val bar = 33
}
Fail.schoko // zero!!
This is an ugly problem which in my personal opinion should go away in future Scala versions by fixing it in the compiler, but yes, currently this is also a reason why one should not use abstract val
s.
Edit (Jan 2016): You are allowed to override an abstract val
declaration with a lazy val
implementation, so that would also prevent the initialisation failure.
Solution 2
I prefer not use val
in traits because the val declaration has unclear and non-intuitive order of initialization. You may add a trait to already working hierarchy and it would break all things that worked before, see my topic: why using plain val in non-final classes
You should keep all things about using this val declarations in mind which eventually road you to an error.
Update with more complicated example
But there are times when you could not avoid using val
. As @0__ had mentioned sometimes you need a stable identifier and def
is not one.
I would provide an example to show what he was talking about:
trait Holder {
type Inner
val init : Inner
}
class Access(val holder : Holder) {
val access : holder.Inner =
holder.init
}
trait Access2 {
def holder : Holder
def access : holder.Inner =
holder.init
}
This code produces the error:
StableIdentifier.scala:14: error: stable identifier required, but Access2.this.holder found.
def access : holder.Inner =
If you take a minute to think you would understand that compiler has a reason to complain. In the Access2.access
case it could not derive return type by any means. def holder
means that it could be implemented in broad way. It could return different holders for each call and that holders would incorporate different Inner
types. But Java virtual machine expects the same type to be returned.
Mansur Ashraf
Updated on May 11, 2021Comments
-
Mansur Ashraf almost 3 years
I was going through the effective scala slides and it mentions on slide 10 to never use
val
in atrait
for abstract members and usedef
instead. The slide does not mention in detail why using abstractval
in atrait
is an anti-pattern. I would appreciate it if someone can explain best practice around using val vs def in a trait for abstract methods -
om-nom-nom over 10 yearswords about tricky initialization order and surprising nulls?
-
0__ over 10 yearsYeah... I would't even go there. True these are also arguments against val, but I think the basic motivation should just be to hide implementation.
-
mplis over 9 yearsThis may have changed in a recent Scala version (2.11.4 as of this comment), but you can override a
val
with alazy val
. Your assertion that you wouldn't be able to createF3
ifbar
was aval
is not correct. That said, abstract members in traits should always bedef
's -
Coder Guy over 9 yearsOrder of initialization shouldn't matter, but instead we get surprising NPE's during runtime, vis-a-vis anti-pattern.
-
ayvango over 9 yearsscala has declarative syntax that hide imperative nature behind. Sometimes that imperativeness works counter-intuitive
-
volia17 almost 9 yearsNo relevant. You have an error too if you use val instead of def (error: reassignment to val), and that's perfectly logical.
-
Dimitry almost 9 yearsNot if you use
var
. The point is, if the they are fields they should be designated as such. I just think having everything asdef
is short sighted. -
Adrian about 8 yearsThe Foo/Fail example works as expected if you replace
val schoko = bar + bar
withlazy val schoko = bar + bar
. That's one way of having some control over the initialization order. Also, usinglazy val
instead ofdef
in the derived class avoids recomputation. -
Khoa about 7 years@om-nom-nom , Could you please elaborate more on initialisation problem or null issue with using val in trait? Any links are much appreciated!
-
om-nom-nom about 7 years@BlueSky well, it's explained a bit in above answer body (check out edit starting with using abstract vals can cause initialisation problems), are you eager for more?
-
Jasper-M almost 7 yearsIf you change
val bar: Int
todef bar: Int
Fail.schoko
is still zero. -
Alexey Romanov almost 7 yearsYes, or you could even have non-abstract
def
orval
which is overridden by aval
or adef
which accesses aval
and run into the same problem. The problem here is using a non-final member during initialization at all, and whether you declare it asdef
or aval
is a red herring. -
Deliganli over 6 yearshappened to me and glad that i find this edit in this post, abstract int vals were being zero exactly it is described here
-
Jono over 5 yearsTo me this says you shouldn't define a
val
in a trait that references other members. Changingval bar: Int
intrait Foo
doesn't solve the initialization problem. In thetrait Foo
example, it's fine to haveval bar: Int
but it isn't fine to haveval schoko = bar + bar
regardless of howbar
is defined. -
Jono over 5 years@Dimitry, sure, using
var
let's you break encapsulation. But using adef
(or aval
) is preferred over a global variable. I think what you're looking for is something likecase class ConcreteEntity(override val id: Int) extends Entity
so that you can create it fromdef create(e: Entity) = ConcreteEntity(1)
This is safer than breaking the encapsulation and allowing any class to change Entity. -
steinybot almost 3 yearsI just realised that rule 1 also applies to classes with concrete
val
s which means that if a classes uses anotherval
anywhere in it's initialisation then the referencedval
has to be final or risknull
s when extended.