Methods in Golang

Methods in Golang

In Golang methods act on types. They are very similar to regular functions except that they take a receiver as a parameter. The receiver is the type which the method will be attached to. This will allow us to do something like myTypevar.doSomething().

For those coming from a language with classes and objects methods in Golang are similar to class member methods. As with regular functions you can pass the receiver by value or by reference. By convention a type’s methods should all be either value or reference parameters. We usually don’t have some methods with value and some with reference parameters.

Example 1: value receiver

https://go.dev/play/p/2urDh8IkY3c

Here’s our first super simple example using a value parameter:

// simple program to demonstrate methods on types
package main

import "fmt"
type myInt int

func (m myInt) addTwo() myInt {
    return m + 2
}

func main() {
    t := myInt(2)
    fmt.Printf("t is %d\r\n", t)

    // call the addTwo method
    t = t.addTwo()
    fmt.Printf("now t is %d\r\n", t)
}

In the above example we create a new type called myInt based on an integer. Note that it starts with a lowercase letter so this is an unexported type, meaning it can only be using in this package. Also, the method, addTwo, is also unexported.

Because our method uses a value receiver we have to return a value and in our main body code we have to reassign the value of t.

Example 2: reference receiver

https://go.dev/play/p/H6Z-P1I5w-8

It would be handy if we didn’t have to reassign the variable (t = t.addTwo()). We can achieve this by using a reference as the receiver. As with regular functions using reference parameters we achieve this using the * operator.

In this example our receiver is a reference type so we can simply call the method without a reassignment.

// simple program to use a reference receiver on the method

package main

import "fmt"

type myType int

func (m *myType) double() {

    *m *= 2

}

func main() {

    t := myType(5)

    fmt.Printf("t is %d\r\n", t)

    t.double()

    fmt.Printf("Now t is %d\r\n", t)

}

Example 3: Validate host name / ip address

https://go.dev/play/p/OI-aeCrQMCd

In this example which is a more realistic example we’ll have a Host type and we’ll add some methods to validate that the host is valid:

// Validate host
package main

import (
	"fmt"
	"net"
	"net/url"
	"regexp"
)

type myHost string

func (h myHost) isValidHost() bool {
	if h.isValidIPv4() {
		return true
	} else if h.isValidIPv6() {
		return true
	} else if h.isValidURL() {
		return true
	}

	return false

}

func (h myHost) isValidIPv4() bool {
	parsedIP := net.ParseIP(string(h))
	return parsedIP != nil && parsedIP.To4() != nil
}

func (h myHost) isValidIPv6() bool {
	re := regexp.MustCompile(`^(([0-9a-fA-F]{1,4}:){7}([0-9a-fA-F]{1,4}|:)|(([0-9a-fA-F]{1,4}:){1,7}:)|(([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4})|(([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2})|(([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3})|(([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4})|(([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5})|([0-9a-fA-F]{1,4}:)((:[0-9a-fA-F]{1,4}){1,6})|(:((:[0-9a-fA-F]{1,4}){1,7}|:))|(::)|(([0-9a-fA-F]{1,4}:){1,4}:[0-9a-fA-F]{1,4})|(([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,6}))$`)
	return re.MatchString(string(h))
}

func (h myHost) isValidURL() bool {
	parsedURL, err := url.Parse(string(h))
	if err != nil {
		return false
	}

	// Check that the scheme is either http or https
	if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
		return false
	}

	// Check that the host is not empty
	if parsedURL.Host == "" {
		return false
	}

	return true

}

func main() {

	// Specific check for IPv4
	host := myHost("192.168.0.1")
	fmt.Printf("host %s is a valid IPv4? %t\r\n", host, host.isValidIPv4())

	// Specific check for IPv6
	host = myHost("2a01:235:133::1")
	fmt.Printf("host %s is a valid IPv6? %t\r\n", host, host.isValidIPv6())

	// Specific check for url
	host = myHost("http://example.com")
	fmt.Printf("host %s is a valid URL? %t\r\n", host, host.isValidURL())

	// Specific check for url
	host = myHost("https://www.example.com")
	fmt.Printf("host %s is a valid URL? %t\r\n", host, host.isValidURL())

	// Generic host check
	host = myHost("somethingelse")
	fmt.Printf("host %s is a valid host? %t\r\n", host, host.isValidHost())

	// Generic host check
	host = myHost("345.34.33.33")
	fmt.Printf("host %s is a valid host? %t\r\n", host, host.isValidHost())

	// Generic host check
	host = myHost("34.34.33.33")
	fmt.Printf("host %s is a valid host? %t\r\n", host, host.isValidHost())

	// Generic host check
	host = myHost("https://www.example.com")
	fmt.Printf("host %s is a valid host? %t\r\n", host, host.isValidHost())

	// Generic host check
	host = myHost("https://www.example.com/mypage")
	fmt.Printf("host %s is a valid host? %t\r\n", host, host.isValidHost())
}

In this example we have a myHost custom type based on a string. It has several methods defined:

  1. isValidIPv4
  2. isValidIPv6
  3. isValidURL
  4. isValidHost

The first three methods are specific checks for IPv4/6 and a url name like http://example.com. The final check checks for either a url or an IPv4/6.

Conclusion

The example demonstrates how to have multiple methods on your golang custom types and also how to call methods from within a method.

Conclusion

Golang makes it easy to add methods onto your custom types. These can use value or reference receivers.

Share

Leave a Reply

Your email address will not be published. Required fields are marked *