Immutable Struct in Golang
Asked Answered
C

4

62

Is it possible to define an immutable struct in Golang? Once initialized then only read operation on struct's field, no modification of field values. If so, how to do that.

Charpentier answered 4/12, 2017 at 11:48 Comment(1)
This was also a good related read for me: flaviocopes.com/go-copying-structsCaen
I
76

It is possible to make a struct read-only outside of its package by making its members non-exported and providing readers. For example:

package mypackage

type myReadOnly struct {
  value int
}

func (s myReadOnly) Value() int {
  return s.value
}

func NewMyReadonly(value int) myReadOnly{
  return myReadOnly{value: value}
}

And usage:

myReadonly := mypackage.NewMyReadonly(3)
fmt.Println(myReadonly.Value())  // Prints 3
Infracostal answered 4/12, 2017 at 12:3 Comment(8)
This is helpful. Is possible to make the struct to be read-only inside of package?Charpentier
No, but since you control the code inside the package all you need to do is simply never write to the structure once it is created.Diadelphous
@VIKRAMSINGHCHOUDHARY although this may be overkill, you could also just move your type into it's own package where there is nothing but it, its exported methods and constructors.Fernery
I believe there are some tradeoffs with this approach.Tampere
What @Fernery said - and leverage Go's internal packages to prevent this package from being used (and depended upon) by others.Inflated
I'm making a struct to emulate a toml configuration file. To parse TOML, we need to export the fields. OTOH, I want to only create the configuration struct at application start so making it read-only makes sense to me. I can't seem to find a solution that addresses both of these.Clubwoman
You can duplicate types. First with public fields for parsing and 2nd for consumption. Though why to worry consumers can change config values if they wish so? It feels unnecessary overreatrictive.Infracostal
@MiloChristiansen - this honestly reads like satire. I'm starting to regret using Go for my weekend project. So many basic features are missing from this language...Erzurum
D
15

There is no way to mark fields/variables as read only in a generic way. The only thing you could do is marking fields/variable as unexported (first letter small) and provide public getters to prevent other packages editing variables.

Death answered 4/12, 2017 at 12:50 Comment(1)
Yet, that does not prevent that for example, another team member working on the same package writes package code that inadvertently changes the field. I really don't see WHY it was so hard for GO language designers to allow const or readonly constructs. I hope GO doesn't go the Python way.Battaglia
P
7

There is no way to define immutable structures in Go: struct fields are mutable and the const keyword doesn't apply to them. Go makes it easy however to copy an entire struct with a simple assignment, so we may think that passing arguments by value is all that is needed to have immutability at the cost of copying.

However, and unsurprisingly, this does not copy values referenced by pointers. And the since built-in collections (map, slice and array) are references and are mutable, copying a struct that contains one of these just copies the pointer to the same underlying memory.

Example :

type S struct {
    A string
    B []string
}

func main() {
    x := S{"x-A", []string{"x-B"}}
    y := x // copy the struct
    y.A = "y-A"
    y.B[0] = "y-B"

    fmt.Println(x, y)
    // Outputs "{x-A [y-B]} {y-A [y-B]}" -- x was modified!
}

Note : So you have to be extremely careful about this, and not assume immutability if you pass a parameter by value.

There are some deepcopy libraries that attempt to solve this using (slow) reflection, but they fall short since private fields can't be accessed with reflection. So defensive copying to avoid race conditions will be difficult, requiring lots of boilerplate code. Go doesn't even have a Clone interface that would standardize this.

Credit : https://bluxte.net/

Pratique answered 3/7, 2020 at 8:40 Comment(4)
This feels crazy to me in 2020. Is the lack of immutability a hot topic in golang or is this just something that systems people don't care about?Lois
I disagree with the "systems people" part. E.g. in Rust, immutable is the default. And yes, it's crazy that Golang does not support this.Basement
@AnthonyNaddeo Among Go's original design goals were: to keep the language light and simple so it's easy to read and compiles fast, and to enable concurrency via lightweight routines and communication channels rather than shared memory. Immutability makes sharing memory cheap and safe, but it adds complexity to the compiler. Since you're not supposed to share memory and the compiler is supposed to be fast, including explicit immutability (or default immutability and opt-in mutability, as Rust does) wasn't a priority originally.Lysander
Slices and maps have internal pointers, yes, but arrays do not. An array in Go (with type such as [3]float32 or [10]int, note the explicit size) is a value and copies just like a struct would. Indeed, [3]int and struct{x,y,z int} have the same memory layout, which is nothing like []int.Lysander
H
-3

if you write a functional struct by golang, it must be an immutable struct, eg you can write maybe struct definite

type Maybe[T any] struct {
    v     T
    valid bool
}

func (m Maybe[T]) Just() T {
    return m.v
}

func (m Maybe[T]) Nothing() bool {
    return m.valid == false
}

func Just[T any](v T) Maybe[T] {
    return Maybe[T]{
        v:     v,
        valid: true,
    }
}

func Nothing[T any]() Maybe[T] {
    return Maybe[T]{
        valid: false,
    }
}

the maybe struct is a immutable struct

Hindbrain answered 12/6, 2022 at 11:44 Comment(3)
This answer does not seem to add much to existing answers. Do you mind adding more details? For example, you can explain the Go 1.18 syntax involved in the code.Selfappointed
I think obvious maybe struct is an immutable struct, what more details are you want?Hindbrain
This is not an immutable struct. The fields of the struct are mutable from within its own package. Given they are not exported, code in other packages cannot directly access them, so cannot mutate them, if you don't provide any mutator functions in their package. So this answer is fundamentally the same as stackoverflow.com/a/47633820 – except it lacks a clear explanation.Librate

© 2022 - 2024 — McMap. All rights reserved.