在golang中使用自己的类型的最佳做法。 联体打造型

I wonder when or if it should be use own type in golang. When this will be more friendly to understand my code, or when I shouldn't use own type.

Example: I want to create map type with MAC, and name host:

in first way the simplest I can do that

var machines map[string]string{
   "11:22...": "myHost",
   "22:33..":  "yourHost",
}

in second way

type MAC string
type HOST string

  machines := map[MAC]HOST{
       MAC("11:22..") : HOST("myHost"),
       MAC("22:33..") : HOST("yourHost"),
    }

In above exmaple I can get additional controle on my type MAC, HOST trought write method to validation, compare etc it is better ?

Third way

type MACHINES map[string]string

m := MACHINES{}
    m = map[string]string{
     "11:22.." : "myHost",
     "22:33" : "yourHost",
    }

above example for me is worst to understand less intuitive to some else. I think that above example should be also filled about HOST, and MAC because type MACHINE nothing to say developer how this should be implement so I would like

  type MACHINES map[MAC]HOST

However, please about comment to better understand about usage own type in golang.

Creating type alias is only useful when you require to add extra methods (such as validation functions) or when you want to document the desired use of some value (for example, the net.IP type).

Type alias could help you to prevent API misunderstanding, but won't if you're using constant values. For example, this code is valid:

type Host string
type Mac string
hosts := map[Mac]Host{"ad:cb..": "localhost"}

For further information about how constants work in Go, you can check the Rob Pike's blog post

One of the most important features of Go is interfaces. In Go by defining the method/s of an Interface you implement the interface, and the only way to implement an interface is by adding a Method to your type. For instance, say you want to implement the Stringer interface from the fmt package.

In your example type MACHINES map[string]string you would add a method called String to your type.

func (m MACHINES) String() string {
    s := "MACHINES
"
    for k, v := range m {
        s += fmt.Sprintf("%v: %v
", k, v)
    }
    return s
}

Any other function that accepts the Stringer interfaces can now also accept your MACHINES type since you implemented the Stringer interface.

For example the fmt.Printf checks if the passed in type implements the Stringer interface

m := MACHINES{"foo":"bar", "baz": "bust"}
fmt.Printf("%s", m)

Will invoke MACHINES String method

Example from the playground

Without commenting on your specific example, there are a few reasons you'd generally want to use a new type:

  • You need to define methods on the type
  • You don't want the type to be comparable with literals or variables with the type it's derived from (eg. to reduce user confusion or make sure they don't do something invalid like attempt to compare your special string with some other random string)
  • You just need a place to put documentation, or to group methods that return a specific type (eg. if you have several Dial methods that return a net.Conn, you might create a type Conn net.Conn and return that instead just for the sake of grouping the functions under the Conn type header in godoc or to provide general documentation for the net.Conn returned by the methods).
  • Because you want people to be able to check if something of a generic type came from your package or not (eg. even if your Conn is just a normal net.Conn, it gives you the option of type switching and checking if it's a yourpackage.Conn as well)
  • You want a function to take an argument from a predefined list of things and you don't want the user to be able to make new values that can be passed in (eg. a list of exported constants of an unexported type)

You do want to use your own types in my opinion.

As an example, lists of function arguments that are all "string" or all "int" are confusing and very easy to get wrong.

And a comment on your type names. "MAC" is an acronym for Media Access Control so it should stay as all caps. But "HOST" should be "Host" since it is just a word. I don't recall where it is exactly, but there's a recommended form for Go names which is CamelCase with all-capital abbreviations, like "GetIPv4AddressFromDNS"

I think another reason to use your own types, that hasn't been mentioned here, is if you are not sure exactly what the right type for something is e.g. uint8 / uint16 and you want to easily change it later all in one place.

This will however make conversions necessary whenever you want to use the built-in types' methods. Or you will need to define them on your own type.