When to use val or def in Scala traits?

28,754

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 vals 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 vals.

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.

Share:
28,754
Mansur Ashraf
Author by

Mansur Ashraf

Updated on May 11, 2021

Comments

  • Mansur Ashraf
    Mansur Ashraf almost 3 years

    I was going through the effective scala slides and it mentions on slide 10 to never use val in a trait for abstract members and use def instead. The slide does not mention in detail why using abstract val in a trait 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
    om-nom-nom over 10 years
    words about tricky initialization order and surprising nulls?
  • 0__
    0__ over 10 years
    Yeah... 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
    mplis over 9 years
    This may have changed in a recent Scala version (2.11.4 as of this comment), but you can override a val with a lazy val. Your assertion that you wouldn't be able to create F3 if bar was a val is not correct. That said, abstract members in traits should always be def's
  • Coder Guy
    Coder Guy over 9 years
    Order of initialization shouldn't matter, but instead we get surprising NPE's during runtime, vis-a-vis anti-pattern.
  • ayvango
    ayvango over 9 years
    scala has declarative syntax that hide imperative nature behind. Sometimes that imperativeness works counter-intuitive
  • volia17
    volia17 almost 9 years
    No relevant. You have an error too if you use val instead of def (error: reassignment to val), and that's perfectly logical.
  • Dimitry
    Dimitry almost 9 years
    Not if you use var. The point is, if the they are fields they should be designated as such. I just think having everything as def is short sighted.
  • Adrian
    Adrian about 8 years
    The Foo/Fail example works as expected if you replace val schoko = bar + bar with lazy val schoko = bar + bar. That's one way of having some control over the initialization order. Also, using lazy val instead of def in the derived class avoids recomputation.
  • Khoa
    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
    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
    Jasper-M almost 7 years
    If you change val bar: Int to def bar: Int Fail.schoko is still zero.
  • Alexey Romanov
    Alexey Romanov almost 7 years
    Yes, or you could even have non-abstract def or val which is overridden by a val or a def which accesses a val and run into the same problem. The problem here is using a non-final member during initialization at all, and whether you declare it as def or a val is a red herring.
  • Deliganli
    Deliganli over 6 years
    happened to me and glad that i find this edit in this post, abstract int vals were being zero exactly it is described here
  • Jono
    Jono over 5 years
    To me this says you shouldn't define a val in a trait that references other members. Changing val bar: Int in trait Foo doesn't solve the initialization problem. In the trait Foo example, it's fine to have val bar: Int but it isn't fine to have val schoko = bar + bar regardless of how bar is defined.
  • Jono
    Jono over 5 years
    @Dimitry, sure, using var let's you break encapsulation. But using a def (or a val) is preferred over a global variable. I think what you're looking for is something like case class ConcreteEntity(override val id: Int) extends Entity so that you can create it from def create(e: Entity) = ConcreteEntity(1) This is safer than breaking the encapsulation and allowing any class to change Entity.
  • steinybot
    steinybot almost 3 years
    I just realised that rule 1 also applies to classes with concrete vals which means that if a classes uses another val anywhere in it's initialisation then the referenced val has to be final or risk nulls when extended.