OK, so here is my suggestion directly inspired by axel22 , Rex Kerr , and Debilski, responding:
class SetOnce[T] { private[this] var value: Option[T] = None def isSet = value.isDefined def ensureSet { if (value.isEmpty) throwISE("uninitialized value") } def apply() = { ensureSet; value.get } def :=(finalValue: T)(implicit credential: SetOnceCredential) { value = Some(finalValue) } def allowAssignment = { if (value.isDefined) throwISE("final value already set") else new SetOnceCredential } private def throwISE(msg: String) = throw new IllegalStateException(msg) @implicitNotFound(msg = "This value cannot be assigned without the proper credential token.") class SetOnceCredential private[SetOnce] } object SetOnce { implicit def unwrap[A](wrapped: SetOnce[A]): A = wrapped() }
We get security at compile time, which := not called by accident, since we need a SetOnceCredential object that returns only once. However, var can be reassigned if the caller has the original credentials. This works with AnyVal and AnyRef s. Implicit conversion allows me to use the variable name directly in many cases, and if that doesn't work, I can explicitly convert it by adding () .
Typical uses would be:
object AppProperties { private val mgr = new SetOnce[FileManager] private val mgr2 = new SetOnce[FileManager] val init = { var inited = false (config: Config) => { if (inited) throw new IllegalStateException("AppProperties already initialized") implicit val mgrCredential = mgr.allowAssignment mgr := makeFileManager(config) mgr2 := makeFileManager(config)
This does not give a compile-time error if, at some other point in the same file, I try to get another accounting parameter and reassign the variable (as in calledAfterInit ), but it does not work at runtime.
source share