Methods and Interfaces

methods and intefaces in golang

Methods

a method is just a function with a receiver argument.

type Vertex struct {
  X, Y float64
}

// Declaring Method with a truct type
func (v Vertex) Abs() float64 {
  return math.Sqrt(v.X * v.X + v.Y * v.Y)
}

v := Vertex{1, 2}
v.Abs() // Calling a Method
----------------------------------

// You can declare a method on non-struct types, too.
func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}
------------------------------

// Mutation with Pointer receivers
// Scale scales the Vertex by a given factor. This method has a pointer receiver (*Vertex).
func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

v := Vertex{3, 4} // Initial Vertex: {X:3 Y:4}
v.Scale(10) // Vertex after scaling: {X:30 Y:40}
v.Abs() // Absolute value after scaling: 50
------------------

p := &v
p.Abs() // the method call p.Abs() is interpreted as (*p).Abs() 

Interfaces

An interface type is defined as a set of method signatures.

package main

import (
	"fmt"
	"math"
)

// 1. Define an interface named Shaper with a single method Area().
type Shaper interface {
	Area() float64
}

// 2. Create a struct Circle that implements the Shaper interface.
type Circle struct {
	Radius float64
}

// 3. Implement the Area method for Circle.
func (c *Circle) Area() float64 {
	if c == nil {
		return 0
	}
	return math.Pi * c.Radius * c.Radius
}

// 4. Create a struct Rectangle that implements the Shaper interface.
type Rectangle struct {
	Width, Height float64
}

// 5. Implement the Area method for Rectangle.
func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

// 6. A function that takes any Shaper and prints its area.
func printArea(s Shaper) {
	if s == nil {
		fmt.Println("Area: 0")
	} else {
		fmt.Printf("Area: %v\n", s.Area())
	}
}

func main() {
	// 7. Create instances of Circle and Rectangle.
	circle := Circle{Radius: 5}
	rectangle := Rectangle{Width: 3, Height: 4}

	// 8. Call the printArea function with Circle and Rectangle instances.
	printArea(&circle)    // Outputs: Area: 78.53981633974483
	printArea(rectangle)  // Outputs: Area: 12
	printArea(nil)       // Outputs: Area: 0

	// 9. Interface values with nil underlying values.
	var nilCircle *Circle
	var i Shaper
	i = nilCircle
	describe(i)          // Outputs: Nil Circle: <nil>, Type: *main.Circle
	printArea(nilCircle) // Outputs: Area: 0

	// 10. The empty interface allows holding values of any type.
	var emptyInterface interface{}
	describe(emptyInterface) // Outputs: (nil, <nil>)

	emptyInterface = 42
	describe(emptyInterface) // Outputs: (42, int)

	emptyInterface = "hello"
	describe(emptyInterface) // Outputs: (hello, string)
}

// 11. A function that describes an interface value.
func describe(i interface{}) {
	fmt.Printf("(%v, %T)\n", i, i)
}

Type Assertions:

package main

import "fmt"

func main() {
	var i interface{} = "hello"

	// Type assertion: Assigns the underlying string value to variable s.
	s := i.(string)
	fmt.Println(s) // Outputs: hello

	// Type assertion with a boolean check.
	s, ok := i.(string)
	fmt.Println(s, ok) // Outputs: hello true

	// Type assertion with a non-matching type, no panic, ok is false, and f is the zero value of float64.
	f, ok := i.(float64)
	fmt.Println(f, ok) // Outputs: 0 false

	// Type assertion with a non-matching type, triggers a panic.
	// f = i.(float64)
	// fmt.Println(f)
}

Type Switches:

package main

import "fmt"

func do(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Printf("Twice %v is %v\n", v, v*2)
	case string:
		fmt.Printf("%q is %v bytes long\n", v, len(v))
	default:
		fmt.Printf("I don't know about type %T!\n", v)
	}
}

func main() {
	do(21)        // Outputs: Twice 21 is 42
	do("hello")   // Outputs: "hello" is 5 bytes long
	do(true)      // Outputs: I don't know about type bool!
}

Stringers:

package main

import "fmt"

// Person is a struct with Name and Age fields.
type Person struct {
	Name string
	Age  int
}

// String method for Person, implementing the Stringer interface.
func (p Person) String() string {
	return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
	// Creating instances of Person.
	a := Person{"Arthur Dent", 42}
	z := Person{"Zaphod Beeblebrox", 9001}

	// Using Stringer interface in fmt.Println.
	fmt.Println(a, z)
	// Outputs: Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)
}

Errors

package main

import (
	"fmt"
	"math"
)

// ErrNegativeSqrt is a custom error type for negative square roots.
type ErrNegativeSqrt float64

// Error method for ErrNegativeSqrt.
func (e ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}

// Sqrt calculates the square root of a number and returns an error for negative input.
func Sqrt(x float64) (float64, error) {
	if x < 0 {
		return 0, ErrNegativeSqrt(x)
	}
	return math.Sqrt(x), nil
}

func main() {
	// Example usage:
	result, err := Sqrt(9)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("Square root:", result)
	}

	result, err = Sqrt(-2)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("Square root:", result)
	}
}

Readers

package main

import (
	"fmt"
	"io"
	"strings"
)

func main() {
	// Create a strings.Reader with the input "Hello, Reader!"
	r := strings.NewReader("Hello, Reader!")

	// Create a byte slice of size 8 to read data into.
	b := make([]byte, 8)

	// Loop to read data from the strings.Reader in chunks of 8 bytes.
	for {
		// Read data into the byte slice.
		n, err := r.Read(b)

		// Print the number of bytes read, the error, and the content of the byte slice.
		fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
		fmt.Printf("b[:n] = %q\n", b[:n])

		// Break the loop if we've reached the end of the stream (io.EOF).
		if err == io.EOF {
			break
		}
	}
}

Last updated