Skip to content
This repository has been archived by the owner on May 9, 2021. It is now read-only.

Why golint treats this as warning? #210

Closed
jeevatkm opened this issue May 17, 2016 · 16 comments
Closed

Why golint treats this as warning? #210

jeevatkm opened this issue May 17, 2016 · 16 comments

Comments

@jeevatkm
Copy link

It's common to use this pattern/structure in go.

type sample struct {
      // fields goes here
}

fun NewSample(/* input variables */) *sample {
    // specific initialize logic goes here
    // this package doesn't export sample struct directly 
    return &s
}

golint warns "exported func NewSample returns unexported type *main.sample, which can be annoying to use (golint)"

@dsymonds
Copy link
Contributor

It's not a common pattern because callers of that function from other packages will struggle to use it. For instance, they can't put a *sample field in their own struct.

@jeevatkm
Copy link
Author

I agree with they can't put a *sample field in their own struct. Thanks, will update my implementation.

@debraj-manna
Copy link

debraj-manna commented Sep 8, 2016

I have a code which looks like below:-

type myMonitor struct {
    isEnabled bool
}

var monitorObj *myMonitor // singleton object

// method to ensure singleton behaviour
func GetInstance() *myMonitor {
    if monitorObj == nil {
        monitorObj = new(myMonitor)
    }
    return monitorObj
}

func (obj *myMonitor) Hello {
 ...
}

golint is reporting warning as:-
warning: exported func GetInstance returns unexported type *monitor.myMonitor, which can be annoying to use (golint)

Should golint report this as warning?

@dsnet
Copy link
Member

dsnet commented Sep 8, 2016

The code snippet you presented is still somewhat strange. Since myMonitor is not exported, it will not be shown in godoc, so the user will not even know about the existence of the myMonitor.Hello method, even if they are able to call it.

Thus, I believe lint is correct in complaining that GetInstance is returning an unexported type.

@debraj-manna
Copy link

I think that is the way to implement singleton as mentioned here

@dsnet
Copy link
Member

dsnet commented Sep 8, 2016

Contrary to what that blog recommends, I think it's a poor idea to make the type of the singleton unexported (again, for the reason that the documentation will not show exported methods on an unexported type).

I would just make myMonitor exported. What is your fear with doing that?

@debraj-spotnana
Copy link

If I make myMonitor as exported then anyone will be able to instantiate it any number of times violating the singleton constraint.

@dsnet
Copy link
Member

dsnet commented Sep 8, 2016

That is a possibility, but you can document it that people should not do as such.

If you want to add the additional boilerplate, you can define an interface:

func GetInstance() Instance { ... }

type Instance interface {
    Hello()
    Goodbye()

    private() // This private method prevents others from implementing this interface
}

This has the advantage that the methods of the singleton are clearly documented. The reason we want to make sure that the interface cannot be implemented is so that you have the future flexibility of adding more methods to the singleton. If other people can implement the interface, you are barred from ever altering the interface.

@sttts
Copy link

sttts commented Oct 25, 2017

There is the valid use-case that an unexperted struct implements multiple interfaces, e.g. Reader and Writer. If this pattern is forbidden, we end up defining union interfaces like ReaderWriter. This is ugly.

@dominikh
Copy link
Member

dominikh commented Oct 25, 2017

@sttts can you elaborate, for example with a concrete example?

If you're suggesting that this

func constructor() *someType { ... }

followed by the client type-asserting o Reader or Writer

is better than this

func constructor() ReaderWriter { ... }

then I have to disagree.

@sttts
Copy link

sttts commented Oct 25, 2017

constructor must be Constructor for it to make sense.

ReaderWriter is a bad example as they are concepts that appear as a pair often. Instead, something like Callback, Reader, Order, all completely unrelated, but implemented by *someType. I don't want CallbackReaderOrder as a generic interface outside of the package for obvious reasons. So maybe the best solution is to define a SomeType interface, along the lines of https://play.golang.org/p/Hf95uVxCML. Wdyt?

@dominikh
Copy link
Member

Yes, that would be the canonical approach, if you can't export the concrete type.

@sttts
Copy link

sttts commented Oct 30, 2017

A local super interface doesn't work unfortunately:

type A interface { Foo() }
type B interface { Foo() }

type C interface {
  A
  B
}

This fails because Foo() is defined twice in C. So we are left with a public struct and private fields.

@ags799
Copy link

ags799 commented Apr 17, 2018

package waterbottle

type WaterBottle interface {
  Liters() int
}

func NewWaterBottle() WaterBottle {
  return &plasticWaterBottle{liters: 1}
}

struct plasticWaterBottle type {
  liters int
}

func (b *plasticWaterBottle) Liters() int {
  return b.liters
}

This is illegal because plasticWaterBottle is unexported.

That's unfortunate: we are trying to encapsulate implementation behind an interface.

A client has no business working with plasticWaterBottle. They only need a WaterBottle.

@sarbash
Copy link

sarbash commented May 16, 2018

@ags799 This is illegal because you mistakenly return value in the constructor, not pointer.

Try this

plasticWaterBottle implements WaterBottle interface so all is ok.

msabramo added a commit to msabramo/go-anysched that referenced this issue Aug 9, 2018
This allows `hyperion` to not have to import `hyperion/marathon`,
`hyperion/kubernetes`, etc. but those things can import `hyperion` now
(without a cyclic import) at that lets the `NewManager` funcs in each to
return a `Manager` interface instead of a pointer to a private struct,
which triggers lint warnings like this:

```
exported func NewManager returns unexported type *dockerswarm.manager, which can be annoying to use
```

and there are some good reasons for that lint warning:
golang/lint#210
@makhov
Copy link

makhov commented Dec 12, 2018

What about this case? Look at these packages:

package user

type storage struct {
      db *sql.DB
}

func New(db *sql.DB) *storage {
      return &storage{db: db}
}

func (s *storage) Set(id int) error { … }
package expuser

type Storage struct {
      db *sql.DB
}

func New(db *sql.DB) *Storage {
      return &storage{db: db}
}

func (s *Storage) Set(id int) error { … }

Lets try to use it (in non-proper way):

package main

import "user"

func main() {
      store := user.storage{}
      store.Set(1)
}

// compile time error: cannot refer to unexported name user.storage

And another one:

package main

import "expuser"

func main() {
      store := expuser.Storage{}
      store.Set(1)
}

// panic: runtime error: invalid memory address or nil pointer dereference

I guess, compile time errors are better than runtime errors.

It's called encapsulation and I can't understand why it's bad?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants