Enforcing the use of constructors in Go

Author : usitvhd
Publish Date : 2021-03-20 18:02:36


Enforcing the use of constructors in Go

Enforcing the use of constructors in Go

4 minutes read. Published: 2021-03-20

When using golang, everyone with access to an exported type can create an instance of it, bypassing the use of a constructor function.

Sometimes though you want to protect you struct data from accidental modifications and keep an always valid internal state.

Or maybe you need to prevent the forging of references (and objects) to follow the object-capability model

Let's see the problem in code

The problem

// file db.go

// Having a File struct means already having Read access 
type File struct {
  Path   string
  Write  bool
  Append bool
  Delete bool
}

func GetFile(path string, userID int) (*File, error) {
  // Check the user permissions...
  userCanRead := false
  if !userCanRead {
    return nil, errors.New("Can't read file")
  }
  return &File{path, false, true, false}, nil
}
// file main.go  

// Using a safe function 
file, err := db.GetFile("/etc/passwd", 0)
fmt.Println("1", file, err)  

// Unsafely creating the struct, violating the invariants
file := db.File{"/etc/passwd", false, true, false} 
fmt.Println(file)

I shouldn't be able to create a struct File manually and neither should I be able to manipulate my permissions.

Solution 1: Unexported type

If a type is unexported, the user of the library can't create it manually The type will be named file instead of File

// file db.go

// Having a file struct means already having Read access 
type file struct {
  Path   string
  Write  bool
  Append bool
  Delete bool
}

func GetFile(path string, userID int) (*file, error) {
  // Return private type *file
}

Problems:

  • External modules can't write functions accepting db.file as argument because it's unexported. I can't even declare a variable of type db.file, so I can't save it in any data structure.

Solution 2: Unexported type + interface

I may create an interface around the file type

type FileIntf interface {
    GetPath()
    CanAppend()
    CanWrite()
    CanDelete()
}

This is not a valid solution though because now I can create a fake type FakeFile and implement this interface, manipulating my permissions. Also, some functions may only accept the internal file type, so I may have to convert between the two, checking every time if the conversion was valid.

Solution 3: Unexported type + wrapper type

// file db.go
type FileWrapper struct {
  file *file
}
func (fileWrapper FileWrapper) func Get() {
  return *fileWrapper.file
}

I can still create a db.FileWrapper with default values (having the file field set to nil). That means using the type is impractical, because every time I use the struct I should check if the internal state was contructed correctly or not.

file := db.FileWrapper{}
// This will fail, because the internal `*file` pointer is nil 
fmt.Println(file.Get())

Solution 4: Unexported type + closure

By saving the data inside an anonymous function I can then pass data around safely, without doing any nil check.

// file db.go
type FileFuncWrap func() file
func (f file) WrapFunc() FileFuncWrap {
  return func() {return f}
}
// file main.go
file := db.GetFile("/etc/passwd", 0)

printFile := func(unwrapFile db.FileFuncWrap) {
  fmt.Println(unwrapFile())
}

// Look! I can even pass it around!
printFile(file.WrapFunc())

Solution 5 (Final): Unexported type + identity interface

The previous solution probably works ok, but creating many anonymous functions everytime you pass data around may be a bit costly. We saw that functions returning an internal type may be useful... Let's try doing something better.

//file db.go

type FileIdentity interface {
  Identity() file
}

// obviously `file` implements the interface
func (f file) Identity() file {
  return f
}

The interface returns the internal file type, so another package can't implement the interface. (That's exactly what I want)

Now we should be able to store a file by saving it as a FileIdentity

//file main.go

file, _ := db.GetFile("/etc/passwd", 0)
arr := []db.FileIdentity{file} // Look how simple it's to store it! Without conversions!
printFile := func(fI db.FileIdentity) {
  fmt.Println(fI.Identity())
}
printFile(arr[0]) // YAY!

A type returning himself should be way faster than creating a closure everytime.

Working code

You can see and run the last example here

https://www.telerealrd.com/advert/free-fight-tv-vergil-ortiz-vs-maurice-hooker-live-free-stream-full-fight-boxing-free/


https://coloradohorseforum.com/advert/fight-tv-vergil-ortiz-vs-maurice-hooker-live-stream-full-fight-boxing-free/


https://www.telerealrd.com/advert/fight-tv-pablo-cesar-cano-vs-jonathan-navarro-live-stream-full-fight-boxing-free/


https://coloradohorseforum.com/advert/free-tv-pablo-cesar-cano-vs-jonathan-navarro-live-stream-full-fight-boxing-free/


https://www.telerealrd.com/advert/freetv-anabel-ortiz-vs-seniesa-estrada-live-stream-full-fight-boxing-free/

 

https://coloradohorseforum.com/advert/boxing-tv-anabel-ortiz-vs-seniesa-estrada-live-stream-full-fight-boxing-free/

 

https://www.telerealrd.com/advert/fight-tv-adam-deines-vs-artur-beterbiev-live-stream-full-fight-boxing-free/

 

https://coloradohorseforum.com/advert/fight-hd-tv-adam-deines-vs-artur-beterbiev-live-stream-full-fight-boxing-free/


https://www.telerealrd.com/advert/fight-tv-anthony-fowler-vs-jorge-fortea-live-stream-full-fight-boxing-free/


https://coloradohorseforum.com/advert/fight-tv-anthony-fowler-vs-jorge-fortea-live-stream-full-fight-boxing-free/


https://vocus.cc/article/60563682fd89780001d58ad6

https://blog.goo.ne.jp/lifetvs/e/d2ee7e2867f44196e20721042885411a

https://paiza.io/projects/xrK1LZCVwd5hkPSdsNyBKg


https://caribbeanfever.com/photo/albums/jut

https://www.deviantart.com/hjfgfg/journal/What-if-you-could-cut-your-hosting-costs-by-over-5-873822569

Conclusion

While the last solution is "cool", I'm still a bit sad... Other languages like Rust and Java don't have the initial problem at all. The object-capability model seems a great way to improve security of my code, but using it in golang isn't so easy.



Category : general

TV[Live];;PSG - MARSEILLE (2021) En Direct Streaming Gratis

TV[Live];;PSG - MARSEILLE (2021) En Direct Streaming Gratis

- Juventus Genoa 13 gennaio 2021 ore 20 45 diretta TV e streaming live Dopo tre vittorie consecutive in campionato la Juve di Andrea Pirlo si campionato l


Practice with Our Unique Salesforce Community-Cloud-Consultant Questions

Practice with Our Unique Salesforce Community-Cloud-Consultant Questions

- Real exam questions in PDF and Practice test format. Download dumps file instantly.


Fresh Amazon CLF-C01 Certification Program In 2021

Fresh Amazon CLF-C01 Certification Program In 2021

- No effective tutorial curriculum, homeschool or standard, is ever comprehensive with out an inventive component. Should the art entails


Palace letters show Queen did not order 1975 removal of Australian Prime Minister Gough Whitlam mediatory

Palace letters show Queen did not order 1975 removal of Australian Prime Minister Gough Whitlam mediatory

- Letters between Kerr and Buckingham Palace were made public after a years-long court battle, but ult