Immutable Values in Ballerina

Maryam Ziyad
Ballerina Swan Lake Tech Blog
4 min readDec 13, 2018

--

The latest release of Ballerina, Ballerina 0.990.0, introduced immutable/read-only values. The main motivation behind introducing immutable values, was to allow safe sharing of values between concurrently executing segments of code.

Ballerina 0.990.0 was a major release that introduced a significant number of new features and changes to the language. Do check out the release notes for Ballerina 0.990.0 for detailed descriptions on the changes.

The .freeze() built-in method

Ballerina introduces the capability to mark a value as immutable, by introducing a built-in method .freeze() on anydata typed values (e.g., array, tuple, map, record, json, xml, table).

If a freeze attempt is successful, the value is marked immutable.

Any and all attempts to update the value from that point onward would result in a panic!

e.g.,

  • entries cannot be added to, updated in or removed from a frozen map
  • values cannot be updated in or added to a frozen array

A freeze attempt on a structured type would be successful only if all the elements of the structure are freezable (i.e., of anydata type), and if successful all the elements would be marked immutable (freezes deeply).

The .isFrozen() built-in method

The boolean returning .isFrozen() built-in method is made available to check if a value is immutable.

This method could be used on potentially frozen values, before you attempt an update, to avoid unnecessary panics.

The official Ballerina by Example on Immutable Values explains/demonstrates the basic usage of the .freeze() and .isFrozen() built-in methods.

Let’s look at a somewhat advanced example based on Ballerina’s stream type.

Streams in Ballerina are constrained types that allow

  • subscription — subscribing to a stream with a function pointer which accepts a value corresponding to the stream’s constraint type.
  • publishing — publishing to the stream, values that correspond to the constraint type.

For example:

Say we have a stream constrained by the int type:

stream<int> intStream = new;

One could subscribe to this stream, by passing a function pointer that accepts an int as its only parameter.

intStream.subscribe(intFunction);

where intFunction is a function that accepts an int as its only parameter.

function intFunction(int i) {
...
}

One could publish int values to this stream using the stream.publish function.

intStream.publish(1);

Every time a value is published to a stream, all the subscribers to the particular stream will receive the event, i.e., all of the function pointers that were registered via the subscribe function would be invoked with the newly published event as the argument.

While there wouldn’t be any issues for simple basic type constrained streams, what about structured type constrained streams?

Say I have a stream constrained by a record type.

With a mutable value, once I publish the particular record to the stream, if one of the subscribers updates a field in the record, it will be reflected across all subscribers.

In the above example, we have two subscriptions to the stream:

  • changeId — just updates the value of the record field eventString to “Updated EventString” as soon as a record is received
  • pauseAndPrintId — pauses for a bit once a record is received, and then prints the value of the eventString field

For demonstration we have created a record with the value of the eventString field set to “Original EventString” and published it to the stream.

When you run this example, the output would be as follow:

$ ballerina run --experimental blog_immutable_values_one.bal
[Subs-1] Received Event
[Subs-2] Received Event
[Subs-1] Updated Event
[Subs-2] Received Event 'eventString': Updated EventString

Note how the value of the eventString field has changed to “Updated EventString” for the second subscription (pauseAndPrintId) too.

But what if I don’t want this to happen? What if I don’t want the subscribers to update the original value?

Immutable values to the rescue!

Now, let’s update the above example a bit, to use immutable values.

Note how we have:

  • called .freeze() on the value before publishing it to the stream (L21)
  • updated the value in the changeId function only if it is not frozen (L31)

Let’s try running the updated version now:

$ ballerina run --experimental blog_immutable_values_two.bal
[Subs-2] Received Event
[Subs-1] Received Event
[Subs-2] Received Event 'eventString': Original EventString

Note how the original value of the eventString has not changed now!

While this is all cool, what if I want to update a value I’ve received? This is indeed a valid requirement.

While not yet supported (as of 0.990.0), a built-in method to support creating an unfrozen copy of a frozen value is in the plans. Thus you could take the safe option of freezing shared values (marking them immutable), while recipients could create their own unfrozen clones of the value, if they want to update the received values.

Hope you found this useful! :)

--

--