Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to disallow direct struct initialization

Tags:

go

Given the following packages in Go, is it possible to prevent the direct initialization of Bar with Bar{..} without de-exposing Bar from the package?

package bar:

package bar

import ()

type Bar struct {
    A string
    B string
}

func NewBar(baz string) Bar{
    return Bar{A:baz, B:baz+baz}
}

package main:

package main

import (
    "fmt"

    "./bar"
)

func main() {
    x := bar.NewBar("sad") //all bars should be created with this
    y := bar.Bar{A: "fadss"} //and this should be disallowed
    bzzBar(x)
    bzzBar(y)
}

func bzzBar(bzz bar.Bar) { //but I can't do 'Bar -> bar' because I want to use the type
    fmt.Println(bzz)
}

My gut feeling says this can't be done, so that is also a valid answer.

like image 933
RickyA Avatar asked Oct 20 '14 09:10

RickyA


4 Answers

There is no way to prevent Bar{} or Bar{A: "foo"}.

To control a struct the way you want you can return an interface instead and not export the struct itself.

Example given:

package bar

type Bar interface{
    A() string
    B() string
    // if you need setters
    SetA(string)
    SetB(string)
}

type bar struct {
    a string
    b string
}

func (b *bar) A() string { return b.a }
func (b *bar) B() string { return b.b }

func (b *bar) SetA(val string) { b.a = val }
func (b *bar) SetB(val string) { b.b = val }

func NewBar(baz string) Bar {
    return &bar{a:baz, b:baz+baz}
}
like image 114
metakeule Avatar answered Nov 12 '22 20:11

metakeule


The idiom used in the Go standard library is:

package bar

package bar

import (
    "fmt"
)

type Bar struct {
    a string
    b string
}

func New(baz string) *Bar {
    return &Bar{a: baz, b: baz + baz}
}

func (b *Bar) BzzBar() {
    fmt.Println(*b)
}

package main

package main

import (
    "bar"
)

func main() {
    x := bar.New("sad") //all bars should be created with this
    x.BzzBar()
    // error: unknown bar.Bar field 'A' in struct literal
    // y := bar.Bar{A: "fadss"} //and this should be disallowed
}

Output:

{sad sadsad}

ADDENDUM:

The Go Programming Language Specification

The zero value

When memory is allocated to store a value, either through a declaration or a call of make or new, and no explicit initialization is provided, the memory is given a default initialization. Each element of such a value is set to the zero value for its type: false for booleans, 0 for integers, 0.0 for floats, "" for strings, and nil for pointers, functions, interfaces, slices, channels, and maps. This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified.

Another idiom used in the Go standard library is to make zero values meaningful. For example, if new has not been explicitly initialized it will have the zero value default of false.

type Bar struct {
    new bool
    a   string
    b   string
}

For example,

package bar

import (
    "fmt"
)

type Bar struct {
    new bool
    a   string
    b   string
}

func New(baz string) *Bar {
    return &Bar{new: true, a: baz, b: baz + baz}
}

func (b *Bar) notnew() {
    if b == nil || !b.new {
        panic("bar.Bar not bar.New")
    }
}

func (b *Bar) Bzz() {
    b.notnew()
    fmt.Println(*b)
}

.

package main

import (
    "bar"
)

func main() {
    x := bar.New("sad") //all bars should be created with this
    x.Bzz()
    // error: unknown bar.Bar field 'A' in struct literal
    // y := bar.Bar{A: "fadss"} //and this should be disallowed

    // var b bar.Bar
    // panic: bar.Bar not bar.New
    // b.Bzz()

    // var b = bar.Bar{}
    // panic: bar.Bar not bar.New
    // b.Bzz()

    // var bp *bar.Bar
    // panic: bar.Bar not bar.New
    // bp.Bzz()

    // var bp = new(bar.Bar)
    // panic: bar.Bar not bar.New
    // bp.Bzz()
}

Output:

{true sad sadsad}
like image 21
peterSO Avatar answered Nov 12 '22 20:11

peterSO


You can make all Bar fields unexported and provide getters and setters for them. This way package users will still be able to do silly things like

a := Bar{}
b := Bar{"foo"}

neither or which seems useful (although the former can be used to create an empty Bar similar to &bytes.Buffer{}).

like image 4
Ainar-G Avatar answered Nov 12 '22 20:11

Ainar-G


You should be able to not export A and B, if you provide a String() function:

type Bar struct {
    a string
    b string
}
func NewBar(baz string) Bar{
    return Bar{a:baz, b:baz+baz}
}
func (Bar) String() string {
  return a + " " b
}
like image 1
VonC Avatar answered Nov 12 '22 20:11

VonC