пятница, 17 июля 2020 г.

Пакет io в Golang, функции чтения

Функция ReadAtLeast

func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)

ReadAtLeast читает из r в buf, пока не прочитает хотя бы min байтов. Он возвращает количество скопированных байтов и ошибку, если было прочитано меньше байтов. Ошибка EOF, только если не было прочитано ни одного байта. Если EOF происходит после считывания менее чем min байтов, ReadAtLeast возвращает ErrUnexpectedEOF. Если min больше, чем длина буфера, ReadAtLeast возвращает ErrShortBuffer. По возвращении n >= min, только если err == nil. Если r возвращает ошибку, прочитав хотя бы min байтов, ошибка сбрасывается.

package main

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

func main() {
    r := strings.NewReader("some io.Reader stream to be read\n")

    buf := make([]byte, 14)
    if _, err := io.ReadAtLeast(r, buf, 4); err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s\n", buf)

    // буфер меньше минимального размера чтения.
    shortBuf := make([]byte, 3)
    if _, err := io.ReadAtLeast(r, shortBuf, 4); err != nil {
        fmt.Println("error:", err)
    }

    // минимальный размер чтения больше, чем поток io.Reader
    longBuf := make([]byte, 64)
    if _, err := io.ReadAtLeast(r, longBuf, 64); err != nil {
        fmt.Println("error:", err)
    }

}

Вывод:

some io.Reader
error: short buffer
error: unexpected EOF

Функция ReadFull

func ReadFull(r Reader, buf []byte) (n int, err error)

ReadFull читает точно len(buf) байтов из r в buf. Он возвращает количество скопированных байтов и ошибку, если было прочитано меньше байтов. Ошибка EOF, только если не было прочитано ни одного байта. Если EOF происходит после чтения некоторых, но не всех байтов, ReadFull возвращает ErrUnexpectedEOF. По возвращении n == len(buf) только тогда, когда err == nil. Если r возвращает ошибку, прочитав по крайней мере len(buf) байтов, ошибка сбрасывается.

package main

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

func main() {
    r := strings.NewReader("some io.Reader stream to be read\n")

    buf := make([]byte, 4)
    if _, err := io.ReadFull(r, buf); err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s\n", buf)

    // минимальный размер чтения больше, чем поток io.Reader
    longBuf := make([]byte, 64)
    if _, err := io.ReadFull(r, longBuf); err != nil {
        fmt.Println("error:", err)
    }

}

Вывод:

some
error: unexpected EOF

Тип Reader

Reader - это интерфейс, который оборачивает базовый метод Read.

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

Read читает до len(p) байтов в p. Он возвращает количество прочитанных байтов (0 <= n <= len(p)) и обнаруженную ошибку. Даже если Read возвращает n < len(p), он может использовать все p как пустое место во время вызова. Если некоторые данные доступны, но не len(p) байтов, Read обычно возвращает то, что доступно, вместо ожидания большего.

Когда Read успешно обнаруживает ошибку или условие конца файла после успешного чтения n > 0 байтов, он возвращает количество прочитанных байтов. Он может вернуть (не nil) ошибку из того же вызова или вернуть ошибку (и n == 0) из последующего вызова. Примером этого общего случая является то, что Reader, возвращающий ненулевое число байтов в конце входного потока, может вернуть либо err == EOF, либо err == nil. Следующее чтение должно вернуть 0, EOF.

Вызывающие всегда должны обрабатывать n > 0 байтов, возвращенных до рассмотрения ошибки err. Это правильно обрабатывает ошибки ввода-вывода, возникающие после чтения некоторых байтов, а также обоих разрешенных вариантов поведения EOF.

Реализации Read не рекомендуется возвращать счетчик нулевых байтов с ошибкой nil, кроме случаев, когда len(p) == 0. Вызывающие операторы должны обрабатывать возврат 0 и nil как указание того, что ничего не произошло; в частности это не указывает на EOF.

Реализации не должны сохранять p (входной срез байтов).

Функция LimitReader

func LimitReader(r Reader, n int64) Reader

LimitReader возвращает Reader, который читает из r, но останавливается с EOF после n байтов. Базовой реализацией является *LimitedReader.

package main

import (
    "io"
    "log"
    "os"
    "strings"
)

func main() {
    r := strings.NewReader("some io.Reader stream to be read\n")
    lr := io.LimitReader(r, 4)

    if _, err := io.Copy(os.Stdout, lr); err != nil {
        log.Fatal(err)
    }

}

Вывод:

some

Функция MultiReader

func MultiReader(readers ...Reader) Reader

MultiReader возвращает Reader, который является логическим объединением предоставленных входных считывателей. Они читаются последовательно. Как только все входы вернут EOF, Read вернет EOF. Если кто-либо из читателей вернет ошибку, отличную от nil, не EOF, Read вернет эту ошибку.

package main

import (
    "io"
    "log"
    "os"
    "strings"
)

func main() {
    r1 := strings.NewReader("first reader ")
    r2 := strings.NewReader("second reader ")
    r3 := strings.NewReader("third reader\n")
    r := io.MultiReader(r1, r2, r3)

    if _, err := io.Copy(os.Stdout, r); err != nil {
        log.Fatal(err)
    }

}

Вывод:

first reader second reader third reader

Функция TeeReader

func TeeReader(r Reader, w Writer) Reader

TeeReader возвращает Reader, который пишет в w то, что он читает из r. Все чтения из r, выполненные через него, сопоставляются с соответствующими записями в w. Внутренняя буферизация отсутствует - запись должна завершиться до завершения чтения. Любая ошибка, обнаруженная во время записи, сообщается как ошибка чтения.

package main

import (
    "bytes"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "strings"
)

func main() {
    r := strings.NewReader("some io.Reader stream to be read\n")
    var buf bytes.Buffer
    tee := io.TeeReader(r, &buf)

    printall := func(r io.Reader) {
        b, err := ioutil.ReadAll(r)
        if err != nil {
            log.Fatal(err)
        }

        fmt.Printf("%s", b)
    }

    printall(tee)
    printall(&buf)

}

Вывод:

some io.Reader stream to be read
some io.Reader stream to be read


Читайте также:


четверг, 16 июля 2020 г.

Пакет io в Golang, функция Pipe

func Pipe() (*PipeReader, *PipeWriter)

Pipe создает синхронный pipe в памяти. Его можно использовать для соединения кода, ожидающего io.Reader, с кодом, ожидающим io.Writer.

Чтения и записи в pipe сопоставляются один к одному, за исключением случаев, когда для чтения одной записи требуется несколько операций чтения. То есть каждый Write в PipeWriter блокируется до тех пор, пока не будет выполнено одна или несколько операций Read из PipeReader, которые полностью используют записанные данные. Данные копируются непосредственно из Write в соответствующие Read (или несколько Read); нет внутренней буферизации.

Безопасно вызывать Read и Write параллельно друг с другом или с помощью Close. Параллельные вызовы на чтение и параллельные вызовы на запись также безопасны: индивидуальные вызовы будут проходить последовательно.

package main

import (
    "bytes"
    "fmt"
    "io"
)

func main() {
    r, w := io.Pipe()

    go func() {
        fmt.Fprint(w, "some text to be read\n")
        w.Close()
    }()

    buf := new(bytes.Buffer)
    buf.ReadFrom(r)
    fmt.Print(buf.String())

}

Вывод:

some text to be read

Тип PipeReader

PipeReader - это считывающая половина pipe.

type PipeReader struct {
    // содержит отфильтрованные или неэкспортированные поля
}

func (r *PipeReader) Close() error

Close закрывает reader; последующие записи в записывающей половине pipe будут возвращать ошибку ErrClosedPipe.

func (r *PipeReader) CloseWithError(err error) error

CloseWithError закрывает reader; последующие записи в записывающей половине pipe будут возвращать err.

CloseWithError никогда не перезаписывает предыдущую ошибку, если она существует, и всегда возвращает nil.

func (r *PipeReader) Read(data []byte) (n int, err error)

Read реализует стандартный интерфейс Read: он читает данные из pipe, блокируя до тех пор, пока не прибудет writer или не закроется конец записи. Если конец записи закрыт с ошибкой, эта ошибка возвращается как err; в противном случае ошибка EOF.

Тип PipeWriter

PipeWriter - это записывающая половина pipe.

type PipeWriter struct {
    // содержит отфильтрованные или неэкспортированные поля
}

func (w *PipeWriter) Close() error

Close закрывает writer; последующие чтения из считывающей половины pipe не будут возвращать байты и вернут EOF.

func (w *PipeWriter) CloseWithError(err error) error

CloseWithError закрывает writer; последующие операции чтения из считывающей половины pipe не будут возвращать байтов и вернут ошибку err, или EOF, если err равно nil.

CloseWithError никогда не перезаписывает предыдущую ошибку, если она существует, и всегда возвращает nil.

func (w *PipeWriter) Write(data []byte) (n int, err error)

Write реализует стандартный интерфейс Write: он записывает данные в pipe, блокируя до тех пор, пока один или несколько считывателей не израсходуют все данные или не будет закрыт конец чтения. Если конец чтения закрыт с ошибкой, эта err возвращается как err; в противном случае ошибка - ErrClosedPipe.


Читайте также:


среда, 15 июля 2020 г.

Пакет io в Golang, функции копирования

Функция Copy

func Copy(dst Writer, src Reader) (written int64, err error)

Copy копирует из src в dst, пока на src не будет достигнут EOF или пока не произойдет ошибка. Возвращает количество скопированных байтов и первую ошибку, обнаруженную при копировании, если таковые имеются.

При успешном копировании возвращается err == nil, а не err == EOF. Поскольку копирование определено для чтения из src до EOF, оно не рассматривает EOF из чтения как ошибку, о которой будет сообщено.

Если src реализует интерфейс WriterTo, копия реализуется путем вызова src.WriteTo(dst). В противном случае, если dst реализует интерфейс ReaderFrom, копия реализуется путем вызова dst.ReadFrom(src).

package main

import (
    "io"
    "log"
    "os"
    "strings"
)

func main() {
    r := strings.NewReader("some io.Reader stream to be read\n")

    if _, err := io.Copy(os.Stdout, r); err != nil {
        log.Fatal(err)
    }

}

Вывод:

some io.Reader stream to be read

Функция CopyBuffer

func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error)

CopyBuffer идентичен Copy, за исключением того, что он поэтапно использует предоставленный буфер (если он необходим), а не выделяет временный. Если buf равен nil, выделяется один новый; в противном случае, если он имеет нулевую длину, CopyBuffer паникует.

Если src реализует WriterTo или dst реализует ReaderFrom, buf не будет использоваться для выполнения копирования.

package main

import (
    "io"
    "log"
    "os"
    "strings"
)

func main() {
    r1 := strings.NewReader("first reader\n")
    r2 := strings.NewReader("second reader\n")
    buf := make([]byte, 8)

    // buf используется здесь...
    if _, err := io.CopyBuffer(os.Stdout, r1, buf); err != nil {
        log.Fatal(err)
    }

    // ... переиспользуется также здесь. 
    // Нет необходимости выделять дополнительный буфер.
    if _, err := io.CopyBuffer(os.Stdout, r2, buf); err != nil {
        log.Fatal(err)
    }

}

Вывод:

first reader
second reader

Функция CopyN

func CopyN(dst Writer, src Reader, n int64) (written int64, err error)

CopyN копирует n байтов (или до ошибки) из src в dst. Возвращает количество скопированных байтов и самую раннюю ошибку, возникшую при копировании. По возвращении written == n только тогда, когда err == nil.

Если dst реализует интерфейс ReaderFrom, копия реализуется с его помощью.

package main

import (
    "io"
    "log"
    "os"
    "strings"
)

func main() {
    r := strings.NewReader("some io.Reader stream to be read")

    if _, err := io.CopyN(os.Stdout, r, 5); err != nil {
        log.Fatal(err)
    }

}

Вывод:

some 


Читайте также:


Пакет io в Golang, ошибки

Пакет io предоставляет базовые интерфейсы для примитивов ввода/вывода. Его основная задача заключается в том, чтобы обернуть существующие реализации таких примитивов, таких как в пакете os, в общие открытые интерфейсы, которые абстрагируют функциональность, а также некоторые другие связанные примитивы.

Поскольку эти интерфейсы и примитивы обертывают низкоуровневые операции различными реализациями, если не сообщено обратное клиенты должны предполагать, что они небезопасны для параллельного выполнения.

Переменные в пакете io

EOF - это ошибка, возвращаемая Read, когда больше нет доступных входных данных. Функции должны возвращать EOF только для того, чтобы сигнализировать о постепенном завершении ввода. Если EOF неожиданно возникает в потоке структурированных данных, соответствующей ошибкой является либо ErrUnexpectedEOF, либо какая-либо другая ошибка, дающая более подробную информацию.

var EOF = errors.New("EOF")

ErrClosedPipe - это ошибка, используемая для операций чтения или записи в закрытом gfqgt.

var ErrClosedPipe = errors.New("io: read/write on closed pipe")

ErrNoProgress возвращается некоторыми клиентами io.Reader, когда многие вызовы Read не смогли вернуть какие-либо данные или ошибки, как правило, признак неправильной реализации io.Reader.

var ErrNoProgress = errors.New("multiple Read calls return no data or error")

ErrShortBuffer означает, что для чтения требовался более длинный буфер, чем было предоставлено.

var ErrShortBuffer = errors.New("short buffer")

ErrShortWrite означает, что запись приняла меньше байтов, чем запрошено, но не смогла вернуть явную ошибку.

var ErrShortWrite = errors.New("short write")

ErrUnexpectedEOF означает, что EOF был обнаружен в середине чтения блока фиксированного размера или структуры данных.

var ErrUnexpectedEOF = errors.New("unexpected EOF")


Читайте также:


суббота, 11 июля 2020 г.

Совместимость ваших модулей

Ваши модули будут развиваться с течением времени по мере добавления новых функций, изменения поведения и пересмотра частей общедоступной поверхности модуля. Как обсуждалось в Go модули: v2 и далее, критические изменения в модуле v1+ должны произойти как часть основного изменения версии (или путем принятия нового пути к модулю).

Однако выпуск новой основной версии затруднен для ваших пользователей. Они должны найти новую версию, изучить новый API и изменить свой код. И некоторые пользователи могут никогда не обновлять, то есть вы должны поддерживать две версии для своего кода навсегда. Поэтому обычно лучше изменить существующий пакет совместимым способом.

В этом посте мы рассмотрим некоторые методы для внесения неразрывных изменений. Общая тема: добавить, не изменять или удалять. Мы также поговорим о том, как с самого начала разработать свой API для совместимости.

Добавление к функции

Часто критические изменения проявляются в форме новых аргументов функции. Мы опишем некоторые способы борьбы с такого рода изменениями, но сначала давайте посмотрим на технику, которая не работает.

При добавлении новых аргументов с разумными значениями по умолчанию заманчиво добавить их в качестве параметра с переменными (variadic) параметрами. Расширить функцию

func Run(name string)

с дополнительным аргументом size, который по умолчанию равен нулю, можно предложить

func Run(name string, size ...int)

на том основании, что все существующие места вызовов будут продолжать работать. Хотя это и так, другие варианты использования Run могут прекратиться, например:

package mypkg
var runner func(string) = yourpkg.Run

Оригинальная функция Run работает здесь, потому что ее тип - func(string), а тип новой функции Run - func(string, ...int), поэтому во время компиляции назначение не выполняется.

Этот пример иллюстрирует, что совместимости вызовов недостаточно для обратной совместимости. На самом деле нет никаких обратно совместимых изменений, которые вы можете внести в сигнатуру функции.

Вместо изменения сигнатуры функции добавьте новую функцию. Например, после введения пакета context стало обычной практикой передавать context.Context в качестве первого аргумента функции. Однако стабильные API не могут изменить экспортированную функцию для принятия context.Context, потому что это нарушит все виды использования этой функции.

Вместо этого были добавлены новые функции. Например, сигнатура метода Query для пакета database/sql была (и остается)

func (db *DB) Query(query string, args ...interface{}) (*Rows, error)

Когда пакет контекста был создан, команда Go добавила новый метод в database/sql:

func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)

Чтобы избежать копирования кода, старый метод вызывает новый:

func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {
    return db.QueryContext(context.Background(), query, args...)
}

Добавление метода позволяет пользователям переходить на новый API в своем собственном темпе. Поскольку методы читаются одинаково и сортируются вместе, а Context - во имя нового метода, это расширение API database/sql не ухудшило читаемость или понимание пакета.

Если вы ожидаете, что функция может нуждаться в большем количестве аргументов в будущем, вы можете планировать заранее, сделав необязательные аргументы частью сигнатуры функции. Самый простой способ сделать это - добавить один аргумент структуры, как это делает функция crypto/tls.Dial:

func Dial(network, addr string, config *Config) (*Conn, error)

Подтверждение связи TLS (TLS handshake), проводимое Dial, требует сети и адреса, но имеет много других параметров с приемлемыми значениями по умолчанию. Передача nil для config использует эти значения по умолчанию; передача структуры Config с некоторыми установленными полями переопределит значения по умолчанию для этих полей. В будущем для добавления нового параметра конфигурации TLS требуется только новое поле в структуре Config - изменение, обратно совместимое (почти всегда).

Иногда техники добавления новой функции и добавления параметров можно объединить, сделав структуру параметров приемником метода. Рассмотрим эволюцию способности пакета net прослушивать сетевой адрес. До перехода к версии 1.11 пакет net предоставлял только функцию Listen с сигнатурой

func Listen(network, address string) (Listener, error)

Для Go 1.11 в сетевое прослушивание были добавлены две функции: передача контекста и разрешение вызывающей стороне предоставлять "функцию управления" для настройки необработанного соединения после создания, но до привязки. Результатом могла быть новая функция, которая принимала контекст, сеть, адрес и функцию управления. Вместо этого авторы пакета добавили структуру ListenConfig в ожидании, что когда-нибудь понадобятся дополнительные параметры. И вместо того, чтобы определять новую функцию верхнего уровня с громоздким именем, они добавили метод Listen к ListenConfig:

type ListenConfig struct {
    Control func(network, address string, c syscall.RawConn) error
}

func (*ListenConfig) Listen(ctx context.Context, network, address string) (Listener, error)

Другим способом предоставления новых параметров в будущем является шаблон "Типы параметров", в котором опции передаются как аргументы с переменным числом аргументов, а каждая опция - это функция, которая изменяет состояние создаваемого значения. Одним из широко используемых примеров является DialOption google.golang.org/grpc.

Типы параметров выполняют ту же роль, что и структура параметров в аргументах функций: они являются расширяемым способом передачи конфигурации, модифицирующей поведение. Решение о том, какой выбрать, во многом зависит от стиля. Рассмотрим это простое использование типа параметра DialOption gRPC:

grpc.Dial("some-target",
  grpc.WithAuthority("some-authority"),
  grpc.WithMaxDelay(time.Second),
  grpc.WithBlock())

Это также может быть реализовано как структура параметров:

notgrpc.Dial("some-target", ¬grpc.Options{
  Authority: "some-authority",
  MaxDelay:  time.Minute,
  Block:     true,
})

Функциональные опции имеют свои недостатки: они требуют написания имени пакета перед опцией для каждого вызова; они увеличивают размер пространства имен пакета; и неясно, каким должно быть поведение, если один и тот же параметр предоставляется дважды. С другой стороны, функциям, которые принимают структуру параметров, требуется параметр, который почти всегда может быть равен нулю, что некоторые считают непривлекательным. И когда нулевое значение типа имеет допустимое значение, неуклюже указывать, что у опции должно быть значение по умолчанию, обычно для этого требуется указатель или дополнительное логическое поле.

Любой из вышеперечисленных способов является разумным выбором для обеспечения в будущем расширяемости общедоступного API вашего модуля.

Работа с интерфейсами

Иногда новые функции требуют изменений в общедоступных интерфейсах: например, интерфейс должен быть расширен новыми методами. Однако непосредственное добавление к интерфейсу является серьезным изменением - как же тогда мы можем поддерживать новые методы в общедоступном интерфейсе?

Основная идея заключается в том, чтобы определить новый интерфейс с новым методом, а затем, где бы ни использовался старый интерфейс, динамически проверять, является ли предоставленный тип более старым или более новым типом.

Давайте проиллюстрируем это на примере из пакета archive/tar. tar.NewReader принимает io.Reader, но со временем команда Go поняла, что было бы более эффективно переходить от одного заголовка файла к другому, если бы вы могли вызвать Seek. Но они не смогли добавить метод Seek в io.Reader: это сломало бы все реализации io.Reader.

Другим исключенным вариантом было изменение tar.NewReader для принятия io.ReadSeeker, а не io.Reader, поскольку он поддерживает как методы io.Reader, так и Seek (посредством io.Seeker). Но, как мы видели выше, изменение сигнатуры функции также является критическим изменением.

Поэтому они решили оставить сигнатуру tar.NewReader неизменной, но проверяют тип для (и поддерживают) io.Seeker в методах tar.Reader:

package tar

type Reader struct {
  r io.Reader
}

func NewReader(r io.Reader) *Reader {
  return &Reader{r: r}
}

func (r *Reader) Read(b []byte) (int, error) {
  if rs, ok := r.r.(io.Seeker); ok {
    // Используем более эффективный rs.Seek.
  }
  // Используем менее эффективный r.r.Read.
}

Когда вы сталкиваетесь со случаем, когда хотите добавить метод в существующий интерфейс, вы можете следовать этой стратегии. Начните с создания нового интерфейса с помощью нового метода или определите существующий интерфейс с помощью нового метода. Затем определите соответствующие функции, которые должны его поддерживать, введите проверку типа второго интерфейса и добавьте код, который его использует.

Эта стратегия работает только тогда, когда старый интерфейс без нового метода все еще может поддерживаться, ограничивая будущую расширяемость вашего модуля.

Там, где это возможно, лучше избегать этого класса проблем полностью. При проектировании конструкторов, например, предпочитают возвращать конкретные типы. Работа с конкретными типами позволяет добавлять методы в будущем, не нарушая пользователей, в отличие от интерфейсов. Это свойство позволяет расширить ваш модуль в будущем.

Совет: если вам нужно использовать интерфейс, но пользователи не собираются его реализовывать, вы можете добавить неэкспортированный метод. Это не позволяет типам, определенным вне вашего пакета, удовлетворять ваш интерфейс без встраивания, позволяя вам добавлять новые методы позже, не нарушая пользовательских реализаций. Например, функция private() в testing.TB.

type TB interface {
    Error(args ...interface{})
    Errorf(format string, args ...interface{})
    // ...

    // private метод для предотвращения 
    // реализации интерфейса пользователями 
    // и поэтому будущие дополнения к нему 
    // не будут нарушать совместимость с Go 1.
    private()
}

Добавить методы конфигурации

До сих пор мы говорили о явных нарушениях, когда изменение типа или функции может привести к тому, что пользовательский код перестанет компилироваться. Однако изменения поведения также могут нарушить работу пользователей, даже если код пользователя продолжает компилироваться. Например, многие пользователи ожидают, что json.Decoder будет игнорировать поля в JSON, которые не входят в структуру аргумента. Когда команда Go хотела вернуть ошибку в этом случае, они должны были быть осторожными. Выполнение этого без механизма согласия означало бы, что многие пользователи, использующие эти методы, могут начать получать ошибки там, где их раньше не было.

Таким образом, вместо изменения поведения для всех пользователей, они добавили метод конфигурации в структуру Decoder: Decoder.DisallowUnknownFields. Вызов этого метода переключает пользователя на новое поведение, но без этого сохраняет прежнее поведение для существующих пользователей.

Поддержание совместимости структур

Выше мы видели, что любое изменение сигнатуры функции является критическим изменением. Ситуация намного лучше со структурами. Если у вас есть экспортированный тип структуры, вы почти всегда можете добавить поле или удалить неэкспортированное поле без нарушения совместимости. При добавлении поля убедитесь, что его нулевое значение имеет смысл и сохраняет старое поведение, чтобы существующий код, который не устанавливает поле, продолжал работать.

Напомним, что авторы пакета net добавили ListenConfig в Go 1.11, потому что они думали, что могут появиться дополнительные опции. Оказывается, они были правы. В Go 1.13 было добавлено поле KeepAlive, чтобы отключить поддержку активности или изменить его период. Значение по умолчанию, равное нулю, сохраняет исходное поведение включения поддержки с периодом по умолчанию.

Есть один тонкий способ, которым новое поле может неожиданно нарушить пользовательский код. Если все типы полей в структуре сопоставимы - то есть значения этих типов можно сравнить с == и != и их можно использовать в качестве ключа карты - тогда общий тип структуры также будет сопоставим. В этом случае добавление нового поля несопоставимого типа сделает общий тип структуры несопоставимым, нарушая любой код, который сравнивает значения этого типа структуры.

Чтобы структура была сопоставимой, не добавляйте в нее несопоставимые поля. Вы можете написать тест для этого или положиться на предстоящий инструмент gorelease, чтобы поймать его.

Во-первых, чтобы предотвратить сравнение, убедитесь, что структура имеет несопоставимое поле. У него уже может быть один - ни один срез, ни карта, ни тип функции не сопоставимы - но если нет, его можно добавить следующим образом:

type Point struct {
        _ [0]func()
        X int
        Y int
}

Тип func() несопоставим, и массив нулевой длины не занимает места. Мы можем определить тип, чтобы прояснить наше намерение:

type doNotCompare [0]func()

type Point struct {
        doNotCompare
        X int
        Y int
}

Вы должны использовать doNotCompare в своих структурах? Если вы определили структуру, которая будет использоваться в качестве указателя, то есть у нее есть методы указателя и, возможно, функция конструктора NewXXX, которая возвращает указатель, то добавление поля doNotCompare, вероятно, является излишним. Пользователи типа указателя понимают, что каждое значение типа отличается: что если они хотят сравнить два значения, они должны сравнить указатели.

Если вы определяете структуру, предназначенную для непосредственного использования в качестве значения, как в нашем примере Point, то довольно часто вы хотите, чтобы она была сопоставимой. В редком случае, когда у вас есть структура значений, которую вы не хотите сравнивать, добавление поля doNotCompare даст вам свободу изменять структуру позже, не беспокоясь о том, чтобы нарушить сравнение. С другой стороны, тип не будет использоваться в качестве ключа карты.

Заключение

При планировании API с нуля тщательно продумайте, насколько расширяемым будет API для новых изменений в будущем. А когда вам нужно добавить новые функции, помните правило: добавляйте, не изменяйте и не удаляйте, помня об исключениях - интерфейсы, аргументы функций и возвращаемые значения не могут быть добавлены обратно совместимыми способами.

Если вам необходимо кардинально изменить API или если API начинает терять свою фокусировку по мере добавления новых функций, возможно, настало время для новой основной версии. Но большую часть времени вносить обратно совместимые изменения легко без причинения боли вашим пользователям.


Читайте также:


купить игрушку gopher

воскресенье, 5 июля 2020 г.

Пакет net в Golang

Пакет net обеспечивает переносимый интерфейс для сетевого ввода-вывода, включая TCP/IP, UDP, разрешение доменных имен и доменные сокеты Unix.

Хотя пакет обеспечивает доступ к низкоуровневым сетевым примитивам, большинству клиентов потребуется только базовый интерфейс, предоставляемый функциями Dial, Listen и Accept и связанными интерфейсами Conn и Listener. Пакет crypto/tls использует те же интерфейсы и аналогичные функции Dial и Listen.

Функция Dial соединяется с сервером:

conn, err := net.Dial("tcp", "golang.org:80")
if err != nil {
    // обработка ошибки
}
fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")
status, err := bufio.NewReader(conn).ReadString('\n')
// ...

Функция Listen создает серверы:

ln, err := net.Listen("tcp", ":8080")
if err != nil {
    // обработка ошибки
}
for {
    conn, err := ln.Accept()
    if err != nil {
        // обработка ошибки
    }
    go handleConnection(conn)
}

Разрешение имени

Метод разрешения доменных имен, будь то косвенно с функциями, такими как Dial, или напрямую с функциями, такими как LookupHost и LookupAddr, зависит от операционной системы.

В системах Unix распознаватель имеет два варианта разрешения имен. Он может использовать чистый распознаватель Go, который отправляет DNS-запросы непосредственно на серверы, перечисленные в /etc/resolv.conf, или он может использовать основанный на cgo распознаватель, который вызывает подпрограммы библиотеки C, такие как getaddrinfo и getnameinfo.

По умолчанию используется чистый распознаватель Go, поскольку заблокированный DNS-запрос потребляет только процедуру, в то время как заблокированный вызов C потребляет поток операционной системы. Когда доступен cgo, вместо него используется распознаватель на основе cgo при различных условиях: в системах, которые не позволяют программам делать прямые запросы DNS (OS X), когда присутствует переменная среды LOCALDOMAIN (даже если она пуста), когда переменная среды RES_OPTIONS или HOSTALIASES является непустой, когда переменная среды ASR_CONFIG не пуста (только OpenBSD), когда /etc/resolv.conf или /etc/nsswitch.conf указывают использование функций, которые не реализует преобразователь Go и когда искомое имя заканчивается на .local или является именем mDNS.

Решение распознавателя может быть отменено путем установки значения netdns переменной среды GODEBUG равным go или cgo, как в:

export GODEBUG=netdns=go    # принудительное использование чистого Go распознавателя
export GODEBUG=netdns=cgo   # принудительное использование cgo распознавателя

Решение также может быть принудительно установлено при построении исходного кода Go путем установки тега сборки netgo или netcgo.

Числовой параметр netdns, например GODEBUG=netdns=1, заставляет распознаватель печатать отладочную информацию о своих решениях. Чтобы принудительно вызвать определенный распознаватель при печати отладочной информации, объедините две настройки знаком плюс, как в GODEBUG=netdns=go+1.

На Plan 9 распознаватель всегда обращается к /net/cs и /net/dns.

В Windows распознаватель всегда использует функции библиотеки C, такие как GetAddrInfo и DnsQuery.


Читайте также:


четверг, 2 июля 2020 г.

Пакет html в Golang

Пакет html предоставляет функции для экранирования и удаления HTML текста.

Функция EscapeString

func EscapeString(s string) string

EscapeString экранирует специальные символы такие как "<" превращая их в "&lt;". Он экранирует только пять таких символов: <, >, &, ', ". UnescapeString(EscapeString(s)) == s всегда выполняется, но обратное не всегда верно.

Пример использования EscapeString

package main

import (
    "fmt"
    "html"
)

func main() {
    const s = `"Fran & Freddie's Diner" `
    fmt.Println(html.EscapeString(s))
}

Вывод:

&#34;Fran &amp; Freddie&#39;s Diner&#34; &lt;tasty@example.com&gt;

Функция UnescapeString

func UnescapeString(s string) string

UnescapeString деэкранирует сущности как "&lt;" в "<". Она деэкранирует больший диапазон сущностей чем экранирует EscapeString. Например, "&aacute;" деэкранируется в "á", также как "&#225;" и "&#xE1;". UnescapeString(EscapeString(s)) == s всегда верно, но обратное не всегда верно.

Пример использования UnescapeString

package main

import (
    "fmt"
    "html"
)

func main() {
    const s = `&quot;Fran &amp; Freddie&#39;s Diner&quot; &lt;tasty@example.com&gt;`
    fmt.Println(html.UnescapeString(s))
}

Вывод:

"Fran & Freddie's Diner" <tasty@example.com>


Читайте также: