[go-nuts] Immutable objects

2,776 views
Skip to first unread message

Esko Luontola

unread,
Apr 23, 2010, 5:45:31 PM4/23/10
to golang-nuts
Since Go is meant for writing concurrent programs, it would IMO
benefit greatly from supporting compiler-enforced immutability,
because concurrent access to immutable objects does not need to be
synchronized. For example you can send a reference to an immutable
object over a channel and be guaranteed that there will be no
concurrency bugs because of concurrent modification. For more
information, see http://en.wikipedia.org/wiki/Immutable_object

The goal of this thread is to make a proposal for structs which can
not be modified after they have been constructed. In particular, this
does not include the immutability of local variables (as discussed in
http://groups.google.com/group/golang-nuts/browse_thread/thread/ba388ecdf547540),
nor is this the same read-only references to mutable objects (as
discussed in http://groups.google.com/group/golang-nuts/browse_thread/thread/aa8d61c0d1502e9d).
(Although this might expand on the ideas in the latter thread by
making the rules for immutable references more strict.)

I have one idea that how immutable objects could be added to the
language. Please point out any cases where this proposal fails to
guarantee immutability, or makes writing code too hard. Constructive
criticism and improvements are welcome.

Proposal:

(1) If a struct type has been labeled as immutable, then an object of
that type can be mutated only in the method where the object is
allocated, before the object is returned from the method or passed as
a parameter to any other method (calling the object's methods is the
same as passing it as a parameter to a method). Everywhere else, any
attempt to modify the object's fields is disallowed by the compiler.

(2) Types which are labeled immutable may contain only other types
which are labeled immutable. There needs to be special handling for
the built-in types (slices, arrays, maps) which makes them immutable
when they are members of an immutable object.

The syntax for labeling things immutable is something that is not yet
considered, until the semantics have been fully thought out. In the
following examples I've used the keyword "immutable".

Some examples of rule (1):

type A immutable struct {
X int
}

func NewA() *A {
a = new(A)
a.X = 1 // allowed, because done in the same method where the
object was allocated (you may even assign it many times if you like)
someOtherMethod(a)
//a.X = 2 // would not be allowed, because the object was already
passed to some other method, and this method is not anymore guaranteed
to have the only reference to the object
return a
}

func (a *A) Foo() {
//a.X = 3 // would not be allowed, because not in the same method
where the object was allocated
}

func Bar() {
a = NewA()
//a.X = 4 // would not be allowed, because not in the same method
where the object was allocated
}

The rule (2) says that immutable objects are fully immutable. If an
object contains a slice, array or map, there needs to be some special
handling which prevents mutating it. I have not yet fully thought it
through, that what all those rules are. Maybe something similar to
rule (1), so that a slice which is labeled immutable may be mutated
only in the method where it is allocated, before it is returned or
passes to any other method.

Some examples of rule (2):

type B immutable struct {
Y *A // allowed, because A is immutable
Z immutable []int // allowed
//W []int // not allowed, because []int is a mutable slice
}

func NewB(values []int) *B {
return &B{NewA(), immutableCopyOf(values)}
}

func immutableCopyOf(values []int) immutable []int {
copy := make([]int, len(values))
for i, v := range values {
copy[i] = v
}
return copy // converting "[]int" to "immutable []int" is allowed
here, because this method is guaranteed to be the only one having a
reference to the slice
}

Similarly also arrays and maps can be made immutable.

It is not yet clear that how interfaces relate to this. For example,
is it allowed for an immutable type to contain interfaces or not.
Maybe if the interface type is labeled immutable ("type Foo immutable
interface {...}") then an immutable struct could contain it. Only
immutable structs may be accessed through immutable interfaces.
Accessing immutable structs through a regular interface is safe,
because the compiler has already enforced that none of the immutable
struct's methods modify itself.

Your thoughts?

Russ Cox

unread,
Apr 23, 2010, 5:48:29 PM4/23/10
to Esko Luontola, golang-nuts
How does immutable differ from the C/C++ const fiasco?

Russ


--
Subscription settings: http://groups.google.com/group/golang-nuts/subscribe?hl=en

Esko Luontola

unread,
Apr 23, 2010, 6:06:45 PM4/23/10
to golang-nuts
On Apr 24, 12:48 am, Russ Cox <r...@golang.org> wrote:
> How does immutable differ from the C/C++ const fiasco?

I haven't used C/C++ much, so please correct if my understandings of
it are wrong.

The "immutable" keyword (or symbol - whatever the syntax) is declared
only once, in the type declaration:

type A immutable struct {
X int
}

Everywhere where A or *A is used, no such keywords are needed. In C++
you need to repeat the "const" everywhere, and even the methods must
be individually declared "const".

Also, AFAIK, in C++ a const reference does not guarantee that the
object is immutable. It only means that you cannot call the non-const
methods of the object. So someone else could have a non-const
reference to the same object, and then mutate it and cause concurrency
bugs. In my proposal, on the other hand, the object is guaranteed to
be immutable after it has been created and passed to some other method.

Esko Luontola

unread,
Apr 23, 2010, 6:15:49 PM4/23/10
to golang-nuts
After reading http://en.wikipedia.org/wiki/Const-correctness I found
also some other differences:

In C++ it's possible to use const_cast to get rid of const. In my
proposal that is not possible.

In C and C++ the const-ness is shallow, meaning that if a struct
contains a pointer to a value, you cannot change the pointer, but you
can change the value to which it points to. In my proposal the
immutability is deep.

jimmy frasche

unread,
Apr 23, 2010, 6:21:23 PM4/23/10
to Esko Luontola, golang-nuts
This creates an immutable point object:

package point

type point struct {
x,y int
}

func (p *point) X() int { return e.x }
func (p *point) Y() int { return e.y }
//etc

func NewPoint(x, y int) *point { return &point{x, y} }

Other than having to type apoint.X() vs. apoint.X what would an
immutable keyword give over what can be done in the language already?
I'm a big fan of immutable data structures but I don't see why the
compiler should be complicated when it can already enforce the same
constraints.

Esko Luontola

unread,
Apr 23, 2010, 7:05:04 PM4/23/10
to golang-nuts
On Apr 24, 1:21 am, jimmy frasche <soapboxcic...@gmail.com> wrote:
> This creates an immutable point object:
>
> package point
>
> type point struct {
>     x,y int
>
> }
>
> func (p *point) X() int { return e.x }
> func (p *point) Y() int { return e.y }
> //etc
>
> func NewPoint(x, y int) *point { return &point{x, y} }
>

It's easy to see that that object can't be modified, because it is to
trivial. But when the object contains non-trivial data-structures and
hundreds of lines of code, it's not anymore possible to see at a
glance, whether it is mutable or immutable. You must _read all code_
in the same _package_ to find out whether it is immutable.

If the same package contains this code, then your point is not anymore
immutable:

package point

func mutateItByAccident(p *point) {
p.x = 1

Peter Thatcher

unread,
Apr 26, 2010, 2:25:03 PM4/26/10
to golang-nuts
I agree that immutable data structures are a missing piece of the
concurrency story in Go. But I think we need to look beyond just
structs that can't be changed. We need vectors, maps, structs, etc,
that are not only immutable but are easily "changed" in the sense that
a new value is created in an efficient way.

I think Clojure (http://clojure.org/) is really leading the way in
this regard with a very impressive implementation of immutable
(technically, "persistent") vectors and maps using hashed array mapped
tries (http://en.wikipedia.org/wiki/Hash_array_mapped_trie). I would
really like to see an implementation of HAMT-based vectors and maps
for Go. In fact, I started one based on the Clojure one, but haven't
had the time to make much progress on it. Is anyone else interested?


On Apr 23, 2:45 pm, Esko Luontola <esko.luont...@gmail.com> wrote:
> Since Go is meant for writing concurrent programs, it would IMO
> benefit greatly from supporting compiler-enforced immutability,
> because concurrent access to immutable objects does not need to be
> synchronized. For example you can send a reference to an immutable
> object over a channel and be guaranteed that there will be no
> concurrency bugs because of concurrent modification. For more
> information, seehttp://en.wikipedia.org/wiki/Immutable_object
>
> The goal of this thread is to make a proposal for structs which can
> not be modified after they have been constructed. In particular, this
> does not include the immutability of local variables (as discussed inhttp://groups.google.com/group/golang-nuts/browse_thread/thread/ba388...),
> nor is this the same read-only references to mutable objects (as
> discussed inhttp://groups.google.com/group/golang-nuts/browse_thread/thread/aa8d6...).

Esko Luontola

unread,
Apr 27, 2010, 6:56:01 AM4/27/10
to golang-nuts
On Apr 26, 9:25 pm, Peter Thatcher <pthatc...@gmail.com> wrote:
> But I think we need to look beyond just
> structs that can't be changed.  We need vectors, maps, structs, etc,
> that are not only immutable but are easily "changed" in the sense that
> a new value is created in an efficient way.

Yes, those are also needed. After the language supports immutable
structs, then implementing immutable data structures is easier. The
usual data structures that functional programming languages have could
be implemented as a library.

An immutable list could implemented as

type List immutable struct {
Value interface{}
Next *List
}

plus some methods for easier access.

chris dollin

unread,
Apr 27, 2010, 7:06:07 AM4/27/10
to Esko Luontola, golang-nuts
On 27 April 2010 11:56, Esko Luontola <esko.l...@gmail.com> wrote:
On Apr 26, 9:25 pm, Peter Thatcher <pthatc...@gmail.com> wrote:
> But I think we need to look beyond just
> structs that can't be changed.  We need vectors, maps, structs, etc,
> that are not only immutable but are easily "changed" in the sense that
> a new value is created in an efficient way.

Yes, those are also needed. After the language supports immutable
structs, then implementing immutable data structures is easier. The
usual data structures that functional programming languages have could
be implemented as a library.

An immutable list could implemented as

type List immutable struct {
   Value interface{}
   Next *List
}

plus some methods for easier access.

If you're going to provide easier access via methods, you don't need to
export the field names, in which case the struct isn't externally mutable
anyway, so the need for "immutable" goes away.

No?

--
Chris "allusive" Dollin

David Roundy

unread,
Apr 27, 2010, 10:46:43 AM4/27/10
to r...@golang.org, Esko Luontola, golang-nuts
I'd like to send a quick me-too. Given that go has an immutable
built-in type, it seems disingenuous to not allow us plebeians to
implement our own immutable types, and the same reasons for having
strings be immutable seem to apply to other types (primarily the
ability to safely pass by reference).

By making the type itself immutable (as opposed to a const keyword),
the "all or nothing" aspect of the const keyword is made explicit.
You either need all your objects to be const nor all non-const. It's
not the most flexible system, but it doesn't fall into the same
problems. Which isn't to say it won't fall into other problems...
--
David Roundy

Kyle Consalus

unread,
Apr 27, 2010, 12:22:57 PM4/27/10
to David Roundy, r...@golang.org, Esko Luontola, golang-nuts
On Tue, Apr 27, 2010 at 7:46 AM, David Roundy <rou...@physics.oregonstate.edu> wrote:
I'd like to send a quick me-too.  Given that go has an immutable
built-in type, it seems disingenuous to not allow us plebeians to
implement our own immutable types, and the same reasons for having
strings be immutable seem to apply to other types (primarily the
ability to safely pass by reference).

Strings are treated as values, and as such are just as immutable as int, float, etc.
I'm not sure we should count being treated as an atomic value type as being immutable.
 

By making the type itself immutable (as opposed to a const keyword),
the "all or nothing" aspect of the const keyword is made explicit.
You either need all your objects to be const nor all non-const.  It's
not the most flexible system, but it doesn't fall into the same
problems.  Which isn't to say it won't fall into other problems...

The notion that all child types of a type must be immutable worries me, as I can easily 
imagine a situation in which a member type of an immutable struct (perhaps one that I don't control) needs to be made mutable again, and suddenly all the immutables that include it are broken.
Or am I misinterpreting the proposed behavior?

Graham

unread,
Apr 27, 2010, 12:15:22 PM4/27/10
to golang-nuts
Supposing all instance objects had 32 bits for flags in its header
(more is also OK).
Some of these could be reserved for system use, some for application
use.

Lots of issues can be resolved with a few extra booleans in the stuct,
but programmers avoid them, because thats expensive, and expansive.
Here (immutability) is a situation, where a boolean, or 3-bit-enum,
might be really useful. Just devise your scheme, and try it out.

Other uses of the bits might be:

GC-marked sweep-bit-1
DTOR-started-in-progress,
RO-access because-of-immutable-type-of-parent
RO-access-but-real-object-is-RW-and-bounced-through-PTR
RW-access-but-copy-on-write
LOCK-of-some-kind
TAINTED-or-safe-bit-4
TAINTED-or-safe-bit-3 ...
ALIAS-currently-object-is-bounced-through-PTR (type will change)
MORPH-this-object-has-moved-and-changed-type-see-PTR
MORE-BITS-REQUIRED-see-associative-lookup-table-to-find-them
EXTRA-RUNTIME-FIELDS-HAVE-BEEN-ADDED (if not ditto)
ALSO-CHECK-PERMISSIONS-bit eg-for-safe-mode-code-compiled-from-script
UDEF-bit-4

Things that the compiler cannot be certain about a compile time,
it could flag, and insert checking code at runtime. That might allow
interesting recipes for shallow-copy, deep-copy, or setup immutability
of the copies/references/other-cases.

Graham

On Apr 23, 9:45 pm, Esko Luontola <esko.luont...@gmail.com> wrote:
> benefit greatly from supporting compiler-enforced immutability,


Esko Luontola

unread,
Apr 27, 2010, 3:46:13 PM4/27/10
to golang-nuts
On Apr 27, 7:22 pm, Kyle Consalus <consa...@gmail.com> wrote:
> The notion that all child types of a type must be immutable worries me, as I
> can easily
> imagine a situation in which a member type of an immutable struct (perhaps
> one that I don't control) needs to be made mutable again, and suddenly all
> the immutables that include it are broken.
> Or am I misinterpreting the proposed behavior?

When an immutable object needs to be modified, a shallow copy of it
with the new values is made and that is returned. For example when
adding a new element to an immutable tree, only the branch to which
the new element is inserted is re-allocated, whereas pointers to the
unmodified branches are re-used. See functional programming.

If an immutable object needs to be converted to a mutable object, a
deep copy of it needs to be made. That is the only option in order to
maintain the safety guarantees of the old immutable object.

Esko Luontola

unread,
Apr 27, 2010, 3:52:19 PM4/27/10
to golang-nuts
On Apr 27, 7:15 pm, Graham <cascade.informat...@googlemail.com> wrote:
> Supposing all instance objects had 32 bits for flags in its header
> (more is also OK).
> Some of these could be reserved for system use, some for application
> use.

I doubt that would fit the Go language, because Go attempts to make it
possible for the programmer to define how the values are stored in
memory - a struct contains only those fields which the programmer says
it contains, and nothing more. Even the type information is not stored
together with the value, but the type information is stored in the
reference - either statically or inside an interface value.

> Here (immutability) is a situation, where a boolean, or 3-bit-enum,
> might be really useful. Just devise your scheme, and try it out.

I'm not sure how that would help.

chris dollin

unread,
Apr 27, 2010, 3:57:54 PM4/27/10
to Esko Luontola, golang-nuts
On 27 April 2010 20:46, Esko Luontola <esko.l...@gmail.com> wrote:

When an immutable object needs to be modified, a shallow copy of it
with the new values is made and that is returned. For example when
adding a new element to an immutable tree, only the branch to which
the new element is inserted is re-allocated,

And all the parent nodes, up to the top of the new tree, which
will be returned as result and passed around (otherwise no-one
gets to see the new tree ...)

whereas pointers to the
unmodified branches are re-used. See functional programming.

Indeed. 

--
Chris "once was functional" Dollin

Kyle Consalus

unread,
Apr 27, 2010, 4:46:05 PM4/27/10
to Esko Luontola, golang-nuts
Sorry, I wasn't talking about runtime behavior, just about the code-maintenance
implications of making a type itself immutable and requiring its members to be the same.

An example of what I'm wondering:

Say you use Point from package geometry, which is immutable.
Neat.
You make 
type Box immutable-struct {
   topleft, bottomright geometry.Point

It all works out, Box is immutable and the compiler ensures that you don't accidentally modify it at any level.

Then, the person who made geometry.Point decides that they need to lazily compute and
cache Point.DistanceFromOrigin(). They change it to no longer be immutable.
Now your code doesn't compile, though the behavior of Point may still be perfectly valid
and logically immutable.

Then again, I suppose the same issue happens when method names or return types are changed on some level.

I would be very interested to see if someone can come up with a detailed proposal for the behavior of immutable data types in Go that fits well with the rest of the language and doesn't complicate it significantly. I've been unable to think of how it would work, but someone probably can.

Esko Luontola

unread,
Apr 28, 2010, 12:01:43 PM4/28/10
to golang-nuts
On Apr 27, 11:46 pm, Kyle Consalus <consa...@gmail.com> wrote:
> Then, the person who made geometry.Point decides that they need to lazily
> compute and
> cache Point.DistanceFromOrigin(). They change it to no longer be immutable.
> Now your code doesn't compile, though the behavior of Point may still be
> perfectly valid
> and logically immutable.

Yes, that is one design decision that needs to be made. In programming
languages there are various degrees of immutability:

- "Relaxed immutability", where the members of an object can not be
changed, but they can refer to mutable data structures. The programmer
is responsible for making sure that immutable objects do not refer to
mutable objects.

- "Strong immutability", where the members of an object can not be
changed, and neither can you change any objects to which they refer
to. The compiler makes sure that immutable objects do not refer to
mutable objects.

In functional languages, relaxed immutability can be seen for example
in Scala, where the case classes (http://www.scala-lang.org/node/107)
are themselves immutable, but the programmer may anyways store a
mutable object inside a case class. Conventions are needed to keep the
objects fully immutable (but it's anyways less work than in languages
like Java). Strong immutability, on the other hand, can be seen for
example in Haskell, where the compiler is very good at enforcing
things.

My personal preference is to have the immutability guarantees to be as
strong as possible, so that it would be harder for a programmer to
shoot himself in the foot. The above proposal tries to achieve this.

-

As a side note, it would be best for the proposal to be generalized so
that, in addition to structs, also types like the following could be
made immutable:

type path []int

This type is from http://github.com/orfjackal/gospec/blob/gospec-1.1.0/src/gospec/specification.go#L77
and it's supposed to be immutable, but right now the language cannot
guarantee it (the type's method declarations are about 50 lines in
total, and it's being used in many places in a codebase of over 1000
lines).

Graham

unread,
Apr 29, 2010, 9:29:24 AM4/29/10
to golang-nuts
> On Apr 27, 7:15 pm, Graham <cascade.informat...@googlemail.com> wrote:
>
> > Supposing all instance objects had 32 bits for flags in its header
> > Some of these could be reserved for system use, some for application

On Apr 27, 8:52 pm, Esko Luontola <esko.luont...@gmail.com> wrote:
...
> memory - a struct contains only those fields which the programmer says
> it contains, and nothing more. Even the type information is not stored

So does malloc() add any bytes to individual heap objects?
Yes it does, probably 8 bytes on a 32 bit sytem.
Is malloc used? Not always, but often.

I agree that this question does question basic tenets, like the
'nothing more' one,
and whilst on the subject, I would also like the type field you
mention,
(but as a packed multi-field-integer, not a pointer, which is not
valid after exit),
and I would also like to have multiple object models in one system,
and I would also like to have runtime knowledge (from compiler) about
how
objects were allocated memory - on stack, or malloc, or sub-slice-of-
array, or not-sure, or ...

Surely this (new go language) is an opportunity to question some
traditional choices.
As well figuring out how to implement, and make use of, fixed choices.

chris dollin

unread,
Apr 29, 2010, 9:38:45 AM4/29/10
to Graham, golang-nuts
On 29 April 2010 14:29, Graham <cascade.i...@googlemail.com> wrote:
> On Apr 27, 7:15 pm, Graham <cascade.informat...@googlemail.com> wrote:
>
> > Supposing all instance objects had 32 bits for flags in its header
> > Some of these could be reserved for system use, some for application

On Apr 27, 8:52 pm, Esko Luontola <esko.luont...@gmail.com> wrote:
...
> memory - a struct contains only those fields which the programmer says
> it contains, and nothing more. Even the type information is not stored

So does malloc() add any bytes to individual heap objects?

That depends.
 
Yes it does, probably 8 bytes on a 32 bit sytem.
Is malloc used? Not always, but often.

Note that a Go struct is /all one object/; if it's mallocated, it's
mallocated in one lump.
 
I agree that this question does question basic tenets, like the
'nothing more' one,
and whilst on the subject, I would also like the type field you
mention,
(but as a packed multi-field-integer, not a pointer, which is not
valid after exit),

Exit from what?

Surely this (new go language) is an opportunity to question some
traditional choices.

It already does.

--
Chris "allusive" Dollin

hong

unread,
May 3, 2010, 1:38:32 AM5/3/10
to golang-nuts
Instead of having mutable types and immutable types, we can use
something similar to C++ reference syntax instead, such as

var p &Point = &&Point{1, 2};

It creates an immutable reference to a struct that nobody can ever
modify. When *Point is converted to &Point, the whole struct is
copied, so it is guaranteed to be immutable. It does not look nice to
me to have Point and ImmutablePoint types and force user to choose one
to use. I think my suggestion is fairly easy to understand for C/C++
users, and relatively simple to implement.

Hong

On Apr 23, 3:15 pm, Esko Luontola <esko.luont...@gmail.com> wrote:
> After readinghttp://en.wikipedia.org/wiki/Const-correctnessI found

Ziad Hatahet

unread,
May 3, 2010, 12:57:19 PM5/3/10
to hong, golang-nuts
I actually like the style Google adopted for using references in C++: they're used to pass objects by const ref as function arguments, otherwise when the arguments are to be modified, a pointer to them is passed: http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Reference_Arguments#Reference_Arguments

I like the proposal by the poster above about references being purely immutable. I don't actually know how hard implementation is though. Anyone know if the language authors have any qualms against explicit/implicit immutable types? The above proposal would do away with requiring the code to be littered with "const" all over like in C++, and Go doesn't support const being cast away as far as I know.

Cheers,

--
Ziad.

Matt LeVeck

unread,
Oct 23, 2014, 5:56:46 AM10/23/14
to golan...@googlegroups.com
Yes.  I'd be interested.  Any code on a github repo?

zero.o...@gmail.com

unread,
May 30, 2015, 5:33:04 PM5/30/15
to golan...@googlegroups.com
Go is a great and immediate language to start working on. Both for developers coming from languages of same family both for others.

It's not functional in nature, but it allows anyway some functional patterns to be expressed.

I too think that being able to force immutable data structures would be great for concurrency and also for programming using functional patterns.

Totally agree.

Regards,
Giacomo
Reply all
Reply to author
Forward
0 new messages