Методы и Интерфейсы

Лекция 3

Максим Иванов

Methods

Пример вызова метода:

const day = 24 * time.Hour
fmt.Println(day.Seconds()) // "86400"

Пример определения метода:

func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
2

Methods

package geometry

import "math"

type Point struct{ X, Y float64 }

// traditional function
func Distance(p, q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}

// same thing, but as a method of the Point type
func (p Point) Distance(q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}
3

Method call

package geometry

import (
	"fmt"
	"testing"
)

func TestPoint(t *testing.T) {
    p := Point{1, 2}
    q := Point{4, 6}
    fmt.Println(Distance(p, q)) // "5", function call
    fmt.Println(p.Distance(q))  // "5", method call
}
4

Path

// A Path is a journey connecting the points with straight lines.
type Path []Point

// Distance returns the distance traveled along the path.
func (path Path) Distance() float64 {
    sum := 0.0
    for i := range path {
        if i > 0 {
            sum += path[i-1].Distance(path[i])
        }
    }
    return sum
}
5

Path

perim := Path{
    {1, 1},
    {5, 1},
    {5, 4},
    {1, 1},
}
fmt.Println(perim.Distance()) // "12"
6

Pointer Receiver

func (p *Point) ScaleBy(factor float64) {
    p.X *= factor
    p.Y *= factor
}
7

Nil receiver

type IntList struct {
    Value int
    Tail *IntList
}

func (list *IntList) Sum() int {
    if list == nil {
        return 0
    }
    return list.Value + list.Tail.Sum()
}
8

Embedding

type Point struct{ X, Y float64 }
type ColoredPoint struct {
    Point
    Color color.RGBA
}

Methods

red := color.RGBA{255, 0, 0, 255}
blue := color.RGBA{0, 0, 255, 255}
var p = ColoredPoint{Point{1, 1}, red}
var q = ColoredPoint{Point{5, 4}, blue}
fmt.Println(p.Distance(q.Point)) // "5"
p.ScaleBy(2)
q.ScaleBy(2)
fmt.Println(p.Distance(q.Point)) // "10"
9

Embedding != Inheritance

var p = ColoredPoint{Point{1, 1}, red}
var q = ColoredPoint{Point{5, 4}, blue}

p.Distance(q) // compile error: cannot use q (ColoredPoint) as Point
10

Embedding: конфликты

// Одинаковые методы у разных встраиваемых типов → конфликт (надо уточнять явно)
type LoggerA struct{}
func (LoggerA) Log(s string) { fmt.Println("A:", s) }

type LoggerB struct{}
func (LoggerB) Log(s string) { fmt.Println("B:", s) }

type S struct {
    LoggerA
    LoggerB
}

var s S
// s.Log("hi") // compile error: ambiguous selector s.Log
s.LoggerA.Log("hi") // A: hi
s.LoggerB.Log("hi") // B: hi
11

Embedding: выбор метода

// Если у внешнего типа и у встраиваемого типа есть методы с одинаковым именем,
// вызов имени без уточнения берётся у внешнего типа.

type Base struct{}
func (Base) Show() { fmt.Println("base") }

type T struct {
  Base
}
func (T) Show() { fmt.Println("outer") }

var t T
t.Show()      // "outer" — берётся метод T.Show
t.Base.Show() // "base" — явное обращение к методу Base
12

Embedding: выбор метода

// Многоуровневое встраивание: выбирается ближайший по уровню метод
type L3 struct{}
func (L3) Show() { fmt.Println("L3") }

type L2 struct{ L3 }
func (L2) Show() { fmt.Println("L2") }

type L1 struct{ L2 }

var x L1
x.Show()       // "L2" — ближе (глубина 1)
x.L2.Show()    // "L2"
x.L2.L3.Show() // "L3"
// Если убрать L2.Show, x.Show() вызовет L3.Show (глубина 2)
13

Embedding: указательные методы и способ встраивания

// Указательные методы (с получателем *T) становятся доступны через переменную-структуру
// только если поле встраивается как указатель или если поле адресуемо.
type Counter struct{ n int }
func (c *Counter) Inc() { c.n++ }

type WrapByValue struct{ Counter }    // встраивание значением
type WrapByPointer struct{ *Counter } // встраивание указателем

var wv WrapByValue
fmt.Println(wv.Counter.n) // 0
// wv.Inc() // compile error: метод с указательным получателем не виден напрямую
wv.Counter.Inc() // OK: у поля есть адрес, метод вызовется на &wv.Counter
fmt.Println(wv.Counter.n) // 1 — значение изменилось внутри wv.Counter
(&wv).Counter.Inc() // эквивалентно; подчёркивает, что нужен указатель
fmt.Println(wv.Counter.n) // 2

var wp WrapByPointer
wp.Counter = &Counter{}
fmt.Println(wp.Counter.n) // 0
wp.Inc() // OK: указательное встраивание делает метод доступным напрямую
fmt.Println(wp.Counter.n) // 1 — изменилось значение по указателю
14

Lock

var cache = struct {
    sync.Mutex
    mapping map[string]string
} {
    mapping: make(map[string]string),
}

func Lookup(key string) string {
    cache.Lock()
    v := cache.mapping[key]
    cache.Unlock()
    return v
}
15

Method Values

p := Point{1, 2}
q := Point{4, 6}

distanceFromP := p.Distance
fmt.Println(distanceFromP(q))
var origin Point
fmt.Println(distanceFromP(origin))

scaleP := p.ScaleBy
scaleP(2)
scaleP(3)
scaleP(10)
16

Method Values

type Rocket struct { /* ... */ }
func (r *Rocket) Launch() { /* ... */ }

r := new(Rocket)
time.AfterFunc(10 * time.Second, func() { r.Launch() })

time.AfterFunc(10 * time.Second, r.Launch)
17

Method Expression

p := Point{1, 2}
q := Point{4, 6}
distance := Point.Distance  // method expression
fmt.Println(distance(p, q)) // "5"
fmt.Printf("%T\n", distance) // "func(Point, Point) float64"
scale := (*Point).ScaleBy
scale(&p, 2)
fmt.Println(p) // "{2 4}"
fmt.Printf("%T\n", scale) // "func(*Point, float64)"
18

Method Values: аргументы

type Point struct{ X, Y float64 }
func (p *Point) MoveBy(dx, dy float64) {
    p.X += dx
    p.Y += dy
}

p := &Point{1, 2}
move := p.MoveBy     // method value; receiver привязан, аргументы остаются
move(3, 4)           // вызывает p.MoveBy(3, 4)
fmt.Println(*p)      // {4 6}

offsetFrom := (*Point).MoveBy // method expression; receiver не привязан
offsetFrom(p, -1, -1)        // явная передача получателя первым аргументом
fmt.Println(*p)              // {3 5}
19

Encapsulation

type Counter struct { n int }

func (c *Counter) N() int { return c.n }
func (c *Counter) Increment() { c.n++ }
func (c *Counter) Reset() { c.n = 0 }

Сеттеры/геттеры для простых типов не приветствуются.

type Point struct { x, y int }

func (p *Point) X() int { return p.x }
func (p *Point) Y() int { return p.y }
func (p *Point) SetX(x int) { p.x = x }
func (p *Point) SetY(y int) { p.y = y }
20

Interfaces

func Fprintf(w io.Writer, format string, args ...any) (int, error)

func Printf(format string, args ...any) (int, error) {
    return Fprintf(os.Stdout, format, args...)
}

func Sprintf(format string, args ...any) string {
    var buf bytes.Buffer
    Fprintf(&buf, format, args...)
    return buf.String()
}
21

Interfaces

// Writer is the interface that wraps the basic Write method.
type Writer interface {
    // Write writes len(p) bytes from p to the underlying data stream.
    // It returns the number of bytes written from p (0 <= n <= len(p))
    // and any error encountered that caused the write to stop early.
    // Write must return a non-nil error if it returns n < len(p).
    // Write must not modify the slice data, even temporarily.
    //
    // Implementations must not retain p.
    Write(p []byte) (n int, err error)
}
22

Interfaces

type ByteCounter int

func (c *ByteCounter) Write(p []byte) (int, error) {
    *c += ByteCounter(len(p)) // convert int to ByteCounter
    return len(p), nil
}

var c ByteCounter
c.Write([]byte("hello"))
fmt.Println(c) // "5", = len("hello")
23

Stringer

package fmt

// The String method is used to print values passed
// as an operand to any format that accepts a string
// or to an unformatted printer such as Print.
type Stringer interface {
    String() string
}
24

Stringer и fmt.Printf

// Если тип реализует fmt.Stringer, форматирование %v и %s используют String().
type User struct {
    ID   int
    Name string
}

func (u User) String() string {
    return fmt.Sprintf("User(%d:%q)", u.ID, u.Name)
}

u := User{ID: 42, Name: "Ada"}
fmt.Printf("%v\n", u)   // User(42:"Ada") — вызван u.String()
fmt.Printf("%s\n", u)   // User(42:"Ada")
fmt.Printf("%#v\n", u)  // main.User{ID:42, Name:"Ada"} — структурный вывод
// Если реализован fmt.GoStringer (метод GoString() string), %#v использует его.
25

Interface types

package io

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}

type ReadWriter interface {
    Reader
    Writer
}

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}
26

Interface embedding in struct

package sort

type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

func Reverse(data Interface) Interface {
    return &reverse{data}
}

type reverse struct {
    // This embedded Interface permits Reverse to use the methods of
    // another Interface implementation.
    Interface
}

func (r reverse) Less(i, j int) bool {
    return r.Interface.Less(j, i)
}
27

Interface satisfaction

var w io.Writer
w = os.Stdout // OK: *os.File has Write method
w = new(bytes.Buffer) // OK: *bytes.Buffer has Write method
w = time.Second // compile error: time.Duration lacks Write method
28

MethodSet

type IntSet struct { /* ... */ }
func (*IntSet) String() string

var s IntSet
var _ = s.String() // OK: s is a variable and &s has a String method

var _ fmt.Stringer = &s // OK
var _ fmt.Stringer = s // compile error: IntSet lacks String method
29

any

type any = interface{}
var v any
v = true
v = 12.34
v = "hello"
v = map[string]int{"one": 1}
v = new(bytes.Buffer)
30

Interface satisfaction

// *bytes.Buffer must satisfy io.Writer
var _ io.Writer = (*bytes.Buffer)(nil)
31

Interface values

var w io.Writer
w = os.Stdout
w = new(bytes.Buffer)
w = nil

var x any = time.Now()
32

Nil pointer

var buf *bytes.Buffer
if debug {
    buf = new(bytes.Buffer) // enable collection of output
}
f(buf) // NOTE: subtly incorrect!

// If out is non-nil, output will be written to it.
func f(out io.Writer) {
    // ...do something...
    if out != nil {
        out.Write([]byte("done!\n"))
    }
}
33

Nil pointer fix

var buf io.Writer
if debug {
    buf = new(bytes.Buffer) // enable collection of output
}
f(buf) // OK
34

Type Assertions

Concrete type:

var w io.Writer
w = os.Stdout
f := w.(*os.File) // success: f == os.Stdout
c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer

Interface type:

var w io.Writer
w = os.Stdout
rw := w.(io.ReadWriter) // success: *os.File has both Read and Write
w = new(ByteCounter)
rw = w.(io.ReadWriter) // panic: *ByteCounter has no Read method
35

Type assertion

var w io.Writer
var rw io.ReadWriter

w = rw // io.ReadWriter is assignable to io.Writer
w = rw.(io.Writer) // fails only if rw == nil

Check type

var w io.Writer = os.Stdout
f, ok := w.(*os.File) // success: ok, f == os.Stdout
b, ok := w.(*bytes.Buffer) // failure: !ok, b == nil
36

Type switches

func sqlQuote(x any) string {
    if x == nil {
        return "NULL"
    } else if _, ok := x.(int); ok {
        return fmt.Sprintf("%d", x)
    } else if _, ok := x.(uint); ok {
        return fmt.Sprintf("%d", x)
    } else if b, ok := x.(bool); ok {
        if b {
            return "TRUE"
        }
        return "FALSE"
    } else if s, ok := x.(string); ok {
        return sqlQuoteString(s) // (not shown)
    } else {
        panic(fmt.Sprintf("unexpected type %T: %v", x, x))
    }
}
37

Type switch

switch x.(type) {
case nil: // ...
case int, uint: // ...
case bool: // ...
case string: // ...
default: // ...
}

switch x := x.(type) { /* ... */ }
38

Type switch

func sqlQuote(x any) string {
    switch v := x.(type) {
    case nil:
        return "NULL"
    case int, uint:
        return fmt.Sprintf("%d", v) // v has type any here.
    case bool:
        if v {
            return "TRUE"
        }
        return "FALSE"
    case string:
        return sqlQuoteString(v) // (not shown)
    default:
        panic(fmt.Sprintf("unexpected type %T: %v", v, v))
    }
}
39

panic, defer, recover

switch s := suit(drawCard()); s {
case "Spades": // ...
case "Hearts": // ...
case "Diamonds": // ...
case "Clubs": // ...
default:
    panic(fmt.Sprintf("invalid suit %q", s)) // Joker?
}

func Reset(x *Buffer) {
    if x == nil {
        panic("x is nil") // unnecessary!
    }
    x.elements = nil
}
40

defer

package copyfile

import (
	"io"
	"os"
)

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }

    written, err = io.Copy(dst, src)
    dst.Close()
    src.Close()
    return
}
41

defer

package copyfile2

import (
	"io"
	"os"
)

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}
42

defer

func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return
}

a()

Output:

0
43

defer

func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}

fmt.Println(b())

Output:

3210
44

defer

func c() (i int) {
    defer func() { i++ }()
    return 1
}

fmt.Println(c())

Output:

2
45

defer

func main() {
    f(3)
}

func f(x int) {
    fmt.Printf("f(%d)\n", x+0/x) // panics if x == 0
    defer fmt.Printf("defer %d\n", x)
    f(x - 1)
}

Output:

f(3)
f(2)
f(1)
defer 1
defer 2
defer 3
46

recover

func Parse(input string) (s *Syntax, err error) {
    defer func() {
        if p := recover(); p != nil {
            err = fmt.Errorf("internal error: %v", p)
        }
    }()
    // ...parser...
}
47

recover

func main() {
    defer func() {
        recover()
        fmt.Println("Checkpoint 1")
        panic(1)
    }()
    defer func() {
        recover()
        fmt.Println("Checkpoint 2")
        panic(2)
    }()
    panic(999)
}

Output:

Checkpoint 2
Checkpoint 1
panic: 999 [recovered]
    panic: 2 [recovered]
    panic: 1
48

errors

type error interface {
    Error() string
}

type Unwrapper interface {
    Unwrap() error
}

Unwrap

errors.Unwrap(fmt.Errorf("... %w ...", ..., err, ...)) == err
49

errors.Is & errors.As

Is

if errors.Is(err, os.ErrExist)

if err == os.ErrExist

As

var perr *os.PathError
if errors.As(err, &perr) {
    fmt.Println(perr.Path)
}

if perr, ok := err.(*os.PathError); ok {
    fmt.Println(perr.Path)
}
50

errors.As

package main

import "errors"

type Temporary interface {
    IsTemporary() bool
}

func do() error { return nil }

func main() {
    err := do()

    var terr Temporary
    if errors.As(err, &terr) && terr.IsTemporary() {
        //...
    }
}
51

Thank you

Максим Иванов

Use the left and right arrow keys or click the left and right edges of the page to navigate between slides.
(Press 'H' or navigate to hide this message.)