#516 Const field checking

brian Thu 9 Apr 2009

I'm making good progress on it-blocks. One of the fundamental aspects of the it-block design is that it-blocks are given permission to set const fields at compile time, but those const field sets are checked at runtime to ensure they only happen at construction time. There are two appproaches for this:

  1. add an inCtor field to each class with const fields and use that for checking
  2. add an inCtor field to the it-block

Option 1 is simple and robust, but adds overhead to every object which I strongly dislike - up until this point Fan adds no overhead to the raw Java/.NET objects.

Option 2 is what I am planning on doing, but it suffers from a couple obscure problems:

  • must pass the it-block in directly as a parameter so that the compiler can generate the inCtor field on enter/exit; so you couldn't do something like pass the it-block inside another structure passed to the constructor
  • if you tried to use the same it-block in multiple instance constructors at the same time you would get really weird errors (although this seems like a really twisted case)

I am planning on pursuing option 2 which will generate code which looks something like this:

// original Fan code
class Foo 
{
  static Void example() { Foo { x = "bar" } }
  new make(|This| f) { f(this) }
  const Str x
}

// generated code
new make(|This| f)
{
  f.inCtor = this
  f.call1(this)
  f.inCtor = null
  return
}

// closure doCall implementation
Void doCall(Foo it)
{
  if (inCtor != it) throw ConstErr("field x")
  it.x = "bar"
}

I am not going to worry about clearing the inCtor flag on exceptions (using a finally block) since its a adds a lot of overhead and an object should never be held by a reference if its ctor raises an exception.

JohnDG Thu 9 Apr 2009

Option 2 is what I am planning on doing, but it suffers from a couple obscure problems:

I do not like these obscure problems because they are likely to take developers (myself included) by complete surprise.

I would prefer something simpler, like if an it block sets const fields, you cannot hold a reference to it -- you must pass it directly to the constructor, and the constructor must call it (or at least, not store a reference to it). This is more restrictive than necessary but has no surprises.

brian Thu 9 Apr 2009

I do not like these obscure problems because they are likely to take developers (myself included) by complete surprise.

Do you really think you would ever have that problem? I can't think of a use case anybody is ever likely to try. And if you did something crazy like passed the same it-block to a sub-objects constructor you would just get ConstErr raised (explaining that the two references aren't equal).

If your suggestion is to try and track who references the it-block, I think that is an impossible problem (at least without a much more sophisticated type system). Can you explain how you think it would work? Remember, to allow builders and immutable setters we need to potentially pass the it-blocks through multiple methods.

qualidafial Thu 9 Apr 2009

What about changing inCtor into the second implicit argument to it-blocks? This way you don't have all the weird threading issues and constraints on how an it-block can be passed around.

JohnDG Thu 9 Apr 2009

Do you really think you would ever have that problem? I can't think of a use case anybody is ever likely to try.

What about a composite it block that applies the children? Or sending an it block to another thread via an Actor?

Can you explain how you think it would work?

Do not allow storing references to const-setting it-blocks. This means you can only pass them to a constructor. If the constructor cannot store a reference to an it-block, or pass it to any function other than a superclass constructor, then there can be no problem -- right???

brian Thu 9 Apr 2009

What about a composite it block that applies the children?

Not sure I understand this - can you provide an example?

Or sending an it block to another thread via an Actor?

There wouldn't be be an issue here (assuming your function was immutable as detected by the compiler).

Do not allow storing references to const-setting it-blocks. This means you can only pass them to a constructor.

That doesn't work, because it preclude passing it-blocks to factory methods and helper methods used to help modify immutable objects. This was one of the awesome things that this feature enables by moving the checks to runtime.

So that restriction seems pretty draconian compared to the minor limitation I am proposing which is only that the it-block function itself has be a parameter and becomes statefully associated with a single object's constructor at a time.

Edit: I guess we could potentially store a stack of objects in the function

qualidafial Thu 9 Apr 2009

One idea: instead of putting a flag in each class (which increases the memory footprint), keep an system-internal map of objects under construction.

  • Objects are added to the map just before passing control to the it block, and removed from the map immediately after returning.
  • In the const field setter (*not* inside the func) consult the map to determine if the map contains the object.
    • If the object is in the "under construction" map, allow the change.
    • Otherwise throw ConstErr

This is performance-intensive (due to addition / removal on the map) but should save on the memory footprint.

Another idea: When an object is at least partially immutable (one or more const fields) pass a mutable view of the immutable object to the it-block. This could take a few different forms:

  • Have the compiler silently emit a subclass which extends the superclass but which internally delegates all behavior to the original object. Field setters in the superclass throw a ConstErr, whereas in the subclass they set the value directly to the field. Upon return from the it-block set a flag in the mutable object causing it to expire.
  • Have the compiler silently emit a subclass which extends the superclass and enables the setters, but do not delegate to the original object. Let the it-block run on the object. Upon return, copy all properties of the mutable object back to the immutable object.

brian Thu 9 Apr 2009

Good ideas Matthew - although both of those approaches have a huge amount of overhead. We need to keep this as lightweight as possible since I might be creating millions of small immutable classes per second.

I implemented the code to do the checks as I originally proposed - you can see the changeset. It isn't a perfect solution, but has an overhead of only a few extra instructions for the construction process and puts the memory overhead (one pointer) into the transient it-block function. So I think of all the various trade-offs this is the sweet spot.

Login or Signup to reply.