Это многостраничный печатный вид этого раздела. Нажмите что бы печатать.
Пакеты языка Go
- 1: Описание пакета html для GO
- 1.1: Описание пакета html/template в Go
- 1.1.1: Шпаргалка по html/template в Go
- 1.1.2: Подробное руководство по define, template и block в html/template
- 1.1.3: Шпаргалка по конвейерам и функциям в html/template (Go)
- 1.1.4: Функции экранирования HTML/JavaScript/URL пакета html/template языка Go
- 1.1.5: Типы экранирования HTML/JavaScript/URL пакета html/template языка Go
- 2: Пакет bufio встроенных функций Go
- 3: Пакет builtin встроенных функций Go
- 3.1: Функции builtin
- 3.2: Типы в пакете builtin
- 4: Описание пакета context языка программирования Go
- 5: Пакет bytes языка программирования Go
- 6: Описание пакета database языка программирования Go
- 6.1: Работа с пакетом database/sql в Go: ошибки и их обработка
- 6.2: Подробное описание функций пакета database/sql в Go
- 6.3: Контекст (context) в транзакциях database/sql
- 6.4: Описание типа database/sql DB
- 6.5: Описание типа database/sql ColumnType
- 6.6: Описание типа Conn database/sql
- 6.7: Описание типа DBStats database/sql
- 6.8: Описание типа Out database/sql
- 6.9: Описание типа Row database/sql
- 6.10: Описание типа Stmt database/sql
- 6.11: Описание типа Tx database/sql
- 7: Описание пакета flag языка программирования Go
- 7.1: Функции пакета flag
- 7.2: Типы пакета flag
- 8: Полный справочник по пакету fmt в Go
- 9: Описание пакета io языка программирования Go
- 9.1: Описание функций пакета io
- 9.2: Описание типов пакета io языка программирования GO
- 9.3: Пакет для работы с файловой системой io/fs
- 10: Пакет Sync для Go
- 11: Пакет os языка программирования Go
- 11.1: Описание функций пакета os
- 11.2: Описание типов пакета os языка программирования Go
- 11.3: Описание функций и типов пакета os/exec языка программирования Go
- 11.4: Пакет os/user языка программирования Go
- 11.5: Пакет os/signal языка программирования Go
- 12: Описание пакета strconv
- 13: Описание пакета error языка программирования Go
- 14: Описание пакета iter языка программирования Go
- 15: Пакет net встроенных функций и типов языка Go
- 15.1: Функции пакета net языка Go
- 15.2: Типы и методы пакета net языка Go
- 15.3: Пакет net/http встроенных функций и типов языка Go
- 15.3.1: Функции пакета net/http языка Go
- 15.3.2: Типы пакета net/http языка Go
- 15.4: Описание пакета для управления маршрутизацией github.com/julienschmidt/httprouter для GO
- 16: Пакет OAuth2 языка программирования Go
- 16.1: Пакет jwt (JSON Web Token)
- 16.2: Пакет jws (JSON Web Signature)
- 16.3: Пакет authhandler (Three-Legged OAuth 2.0)
- 16.4: Пакет clientcredentials OAuth 2.0 Client Credentials Flow
- 17: Описание пакета string языка программирования Go
- 17.1: Функции пакета string языка программирования Go
- 17.2: Описание типов пакета string языка программирования Go
- 18: Полное описание пакета log в Go
- 19: Описание пакета unsafe языка программирования Go
- 20: Пакет time языка программирования Go
- 21: SCS: Управление HTTP-сессиями для Go
1 - Описание пакета html для GO
Пакет html предоставляет функции для экранирования и отмены экранирования HTML-текста.
func EscapeString
func EscapeString(s string) string
EscapeString экранирует специальные символы, такие как “<”, превращаясь в “<”. Он экранирует только пять таких символов: <, >, &, ’ и “. UnescapeString(EscapeString(s)) == s всегда выполняется, но обратное не всегда верно.
Пример
package main
import (
"fmt"
"html"
)
func main() {
const s = `"Fran & Freddie's Diner" <tasty@example.com>`
fmt.Println(html.EscapeString(s))
}
Output:
`"Fran & Freddie's Diner" <tasty@example.com>`
func UnescapeString
func UnescapeString(s string) string
UnescapeString отменяет экранирование таких сущностей, как “<”, чтобы они стали “<”. Он отменяет экранирование большего диапазона сущностей, чем EscapeString. Например, “á” отменяет экранирование на “á”, как и “á” и “á”. UnescapeString(EscapeString(s)) == s всегда выполняется, но обратное не всегда верно.
Пример
package main
import (
"fmt"
"html"
)
func main() {
const s = `"Fran & Freddie's Diner" <tasty@example.com>`
fmt.Println(html.UnescapeString(s))
}
Output:
"Fran & Freddie's Diner" <tasty@example.com>
1.1 - Описание пакета html/template в Go
Пакет html/template реализует шаблоны, управляемые данными, для генерации HTML-вывода, защищённого от внедрения кода. Он предоставляет тот же интерфейс, что и text/template, и должен использоваться вместо него, когда выводом является HTML.
Данная документация фокусируется на функциях безопасности пакета. Информацию о программировании самих шаблонов см. в документации text/template.
Введение
Этот пакет оборачивает text/template, позволяя использовать его API шаблонов для безопасного разбора и выполнения HTML-шаблонов.
Пример:
tmpl, err := template.New("name").Parse(...)
// Проверка ошибок опущена
err = tmpl.Execute(out, data)
Если операция успешна, tmpl будет защищён от инъекций. В противном случае err будет содержать ошибку, как описано в ErrorCode.
HTML-шаблоны обрабатывают значения данных как обычный текст, который должен быть закодирован для безопасного встраивания в HTML-документ. Экранирование является контекстно-зависимым, поэтому действия могут появляться в JavaScript, CSS и URI-контекстах.
Модель безопасности, используемая этим пакетом, предполагает, что авторы шаблонов являются доверенными, а параметр данных Execute - нет. Более подробная информация приведена ниже.
import "text/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
Вывод
Hello, <script>alert('you have been pwned')</script>!
но контекстное автоэкранирование в html/шаблоне
import "html/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
создает безопасный, экранированный HTML-вывод
Hello, <script>alert('you have been pwned')</script>!
Контексты
Этот пакет понимает HTML, CSS, JavaScript и URI. Он добавляет функции очистки к каждому простому конвейеру действий, поэтому, рассмотрим пример
<a href="/search?q={{.}}">{{.}}</a>
Во время разбора каждый {{.}} перезаписывается для добавления экранирующих функций по мере необходимости. В этом случае это становится
<a href="/search?q={{. | urlescaper | attrescaper}}">{{. | htmlescaper}}</a>
где urlescaper, attrescaper и htmlescaper — псевдонимы для внутренних функций экранирования.
Для этих внутренних функций экранирования, если конвейер действий оценивает значение интерфейса nil, он обрабатывается так, как будто это пустая строка.
Namespaced и data-атрибуты
Атрибуты с пространством имен обрабатываются так, как будто у них нет пространства имен. Рассмотрим пример:
<a my:href="{{.}}"></a>
Во время анализа атрибут будет обработан так, как если бы это был просто “href”. Таким образом, во время анализа шаблон становится:
<a my:href="{{. | urlescaper | attrescaper}}"></a>
Аналогично атрибутам с пространствами имен, атрибуты с префиксом “data-” обрабатываются так, как если бы у них не было префикса “data-”. Рассмотрим пример:
<a data-href="{{.}}"></a>
Во время анализа это становится
<a data-href="{{. | urlescaper | attrescaper}}"></a>
Если атрибут имеет как пространство имен, так и префикс “data-”, то при определении контекста будет удалено только пространство имен. Например:
<a my:data-href="{{.}}"></a>
Это обрабатывается так, как если бы “my:data-href” было просто “data-href”, а не “href”, как было бы, если бы префикс “data-” тоже игнорировался. Таким образом, во время анализа это становится просто
<a my:data-href="{{. | attrescaper}}"></a>
В качестве особого случая атрибуты с пространством имен “xmlns” всегда рассматриваются как содержащие URL-адреса. Например:
<a xmlns:title="{{.}}"></a>
<a xmlns:href="{{.}}"></a>
<a xmlns:onclick="{{.}}"></a>
Во время анализа они становятся:
<a xmlns:title="{{. | urlescaper | attrescaper}}"></a>
<a xmlns:href="{{. | urlescaper | attrescaper}}"></a>
<a xmlns:onclick="{{. | urlescaper | attrescaper}}"></a>
Ошибки
Подробности см. в документации ErrorCode.
Более полная картина
Остальную часть этого комментария к пакету можно пропустить при первом чтении; он включает детали, необходимые для понимания экранирования контекстов и сообщений об ошибках. Большинству пользователей не нужно понимать эти детали.
Контексты
Предположив, что {{.}} — это O'Reilly: How are <i>you</i>?, в таблице ниже показано, как выглядит {{.}} при использовании в контексте слева.
Context {{.}} After
{{.}} O'Reilly: How are <i>you</i>?
<a title='{{.}}'> O'Reilly: How are you?
<a href="/{{.}}"> O'Reilly: How are %3ci%3eyou%3c/i%3e?
<a href="?q={{.}}"> O'Reilly%3a%20How%20are%3ci%3e...%3f
<a onx='f("{{.}}")'> O\x27Reilly: How are \x3ci\x3eyou...?
<a onx='f({{.}})'> "O\x27Reilly: How are \x3ci\x3eyou...?"
<a onx='pattern = /{{.}}/;'> O\x27Reilly: How are \x3ci\x3eyou...\x3f
При использовании в небезопасном контексте значение может быть отфильтровано:
Context {{.}} After
<a href="{{.}}"> #ZgotmplZ
поскольку “O’Reilly:” не является разрешенным протоколом, как “http:”.
Если {{.}} — это безобидное слово left, то оно может появляться более широко,
Context {{.}} After
{{.}} left
<a title='{{.}}'> left
<a href='{{.}}'> left
<a href='/{{.}}'> left
<a href='?dir={{.}}'> left
<a style="border-{{.}}: 4px"> left
<a style="align: {{.}}"> left
<a style="background: '{{.}}'> left
<a style="background: url('{{.}}')> left
<style>p.{{.}} {color:red}</style> left
Нестроковые значения могут использоваться в контекстах JavaScript. Если {{.}} — это
struct{A,B string}{ "foo", "bar" }
в экранированном шаблоне
<script>var pair = {{.}};</script>
то вывод шаблона будет
<script>var pair = {"A": "foo", "B": "bar"};</script>
Ознакомьтесь с пакетом json, чтобы понять, как нестроковый контент маршалируется для встраивания в контексты JavaScript.
Типизированные строки
По умолчанию этот пакет предполагает, что все конвейеры производят простую текстовую строку. Он добавляет этапы экранирования конвейера, необходимые для корректного и безопасного встраивания этой простой текстовой строки в соответствующий контекст.
Если значение данных не является обычным текстом, вы можете убедиться, что оно не экранировано слишком сильно, пометив его разными типами.
Типы HTML, JS, URL и другие из content.go могут содержать безопасный контент, который освобожден от экранирования.
Например шаблон:
Hello, {{.}}!
может быть вызван с помощью
tmpl.Execute(out, template.HTML(`<b>World</b>`))
вывод
Hello, <b>World</b>!
вместо
Hello, <b>World<b>!
который был бы получен, если бы {{.}} был обычной строкой.
Модель безопасности
Определение “безопасности”, используемое в этом пакете, основано на спецификации.
Данный пакет исходит из следующих допущений:
- Авторы шаблонов считаются доверенными
- Данные, передаваемые в
Execute, — ненадёжные
При этом обеспечиваются следующие свойства безопасности при работе с ненадёжными данными:
1. Сохранение структуры (Structure Preservation Property)
Если автор шаблона пишет HTML-тег на безопасном языке шаблонов, браузер всегда будет интерпретировать соответствующую часть вывода именно как тег — независимо от значений ненадёжных данных. То же самое относится к другим структурам, таким как границы атрибутов, строки JavaScript и CSS.
2. Контроль исполнения кода (Code Effect Property)
В результате вставки вывода шаблона на страницу должен выполняться только код, явно указанный автором шаблона. При этом весь код, указанный автором, должен выполняться корректно.
3. Принцип наименьшего удивления (Least Surprise Property)
Разработчик (или ревьюер кода), знакомый с HTML, CSS и JavaScript и знающий о контекстном автоэкранировании, должен быть способен, увидев
{{.}}, однозначно определить, какое экранирование будет применено.
Поддержка шаблонных литералов ES6
Ранее шаблонные литералы ECMAScript 6 (${...}) были отключены по умолчанию, и их можно было включить через переменную окружения GODEBUG=jstmpllitinterp=1.
Сейчас шаблонные литералы поддерживаются по умолчанию, а настройка jstmpllitinterp больше не имеет эффекта.
Пример
package main
import (
"html/template"
"log"
"os"
)
func main() {
const tpl = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{{.Title}}</title>
</head>
<body>
{{range .Items}}<div>{{ . }}</div>{{else}}<div><strong>no rows</strong></div>{{end}}
</body>
</html>`
check := func(err error) {
if err != nil {
log.Fatal(err)
}
}
t, err := template.New("webpage").Parse(tpl)
check(err)
data := struct {
Title string
Items []string
}{
Title: "My page",
Items: []string{
"My photos",
"My blog",
},
}
err = t.Execute(os.Stdout, data)
check(err)
noItems := struct {
Title string
Items []string
}{
Title: "My another page",
Items: []string{},
}
err = t.Execute(os.Stdout, noItems)
check(err)
}
Output:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>My page</title>
</head>
<body>
<div>My photos</div><div>My blog</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>My another page</title>
</head>
<body>
<div><strong>no rows</strong></div>
</body>
</html>Пример Autoescaping
package main
import (
"html/template"
"log"
"os"
)
func main() {
check := func(err error) {
if err != nil {
log.Fatal(err)
}
}
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
check(err)
err = t.ExecuteTemplate(os.Stdout, "T", "<script>alert('you have been pwned')</script>")
check(err)
}
Output:
Hello, <script>alert('you have been pwned')</script>!
Пример Escape
package main
import (
"fmt"
"html/template"
"os"
)
func main() {
const s = `"Fran & Freddie's Diner" <tasty@example.com>`
v := []any{`"Fran & Freddie's Diner"`, ' ', `<tasty@example.com>`}
fmt.Println(template.HTMLEscapeString(s))
template.HTMLEscape(os.Stdout, []byte(s))
fmt.Fprintln(os.Stdout, "")
fmt.Println(template.HTMLEscaper(v...))
fmt.Println(template.JSEscapeString(s))
template.JSEscape(os.Stdout, []byte(s))
fmt.Fprintln(os.Stdout, "")
fmt.Println(template.JSEscaper(v...))
fmt.Println(template.URLQueryEscaper(v...))
}
Output:
"Fran & Freddie's Diner" <tasty@example.com>
"Fran & Freddie's Diner" <tasty@example.com>
"Fran & Freddie's Diner"32<tasty@example.com>
\"Fran \u0026 Freddie\'s Diner\" \u003Ctasty@example.com\u003E
\"Fran \u0026 Freddie\'s Diner\" \u003Ctasty@example.com\u003E
\"Fran \u0026 Freddie\'s Diner\"32\u003Ctasty@example.com\u003E
%22Fran+%26+Freddie%27s+Diner%2232%3Ctasty%40example.com%3E
1.1.1 - Шпаргалка по html/template в Go
Инициализация шаблона
import "html/template"
// Создание нового шаблона
tmpl := template.New("templateName")
// Парсинг шаблона из строки
tmpl, err := template.New("test").Parse(`<h1>{{.Title}}</h1>`)
// Парсинг шаблона из файла
tmpl, err := template.ParseFiles("template.html")
// Парсинг нескольких файлов
tmpl, err := template.ParseGlob("templates/*.html")
Основные конструкции в шаблоне
1. Вывод значения
<!-- Простой вывод -->
<p>{{.UserName}}</p>
<!-- Вывод с экранированием HTML -->
<p>{{.UserComment}}</p>
<!-- Вывод без экранирования (осторожно!) -->
<p>{{.SafeHTML | html}}</p>
2. Условия
{{if .User}}
<p>Welcome, {{.User}}!</p>
{{else}}
<p>Please log in.</p>
{{end}}
{{if eq .Role "admin"}}
<p>Admin dashboard</p>
{{end}}
3. Циклы
<ul>
{{range .Items}}
<li>{{.}}</li>
{{end}}
</ul>
<!-- С индексом -->
<ul>
{{range $index, $item := .Items}}
<li>Item #{{$index}}: {{$item}}</li>
{{end}}
</ul>
<!-- Если слайс пуст -->
{{range .Items}}
{{.}}
{{else}}
<p>No items found</p>
{{end}}
4. Определение переменных
{{$name := .UserName}}
{{$count := len .Items}}
<p>Hello, {{$name}}. You have {{$count}} items.</p>
5. Функции и конвейеры
<!-- Вызов встроенных функций -->
<p>{{printf "Hello, %s" .Name}}</p>
<p>{{len .Items}} items</p>
<p>{{index .Items 0}}</p>
<!-- Конвейер -->
<p>{{.Title | upper | truncate 50}}</p>
6. Вложенные шаблоны
// Определение в коде
tmpl := template.Must(template.New("base").Parse(`
{{define "content"}}Default content{{end}}
<html>
<body>
{{template "content" .}}
</body>
</html>
`))
// Переопределение в другом шаблоне
tmpl2 := template.Must(tmpl.Parse(`{{define "content"}}New content{{end}}`))
<!-- В шаблоне -->
{{template "header" .}}
<!-- С передачей других данных -->
{{template "footer" .FooterData}}
7. Пользовательские функции
funcMap := template.FuncMap{
"upper": strings.ToUpper,
"add": func(a, b int) int { return a + b },
}
tmpl := template.New("test").Funcs(funcMap)
tmpl, err := tmpl.Parse(`{{upper .Name}} {{add 1 2}}`)
Пример полного шаблона
<!DOCTYPE html>
<html>
<head>
<title>{{.Title}} - My Site</title>
</head>
<body>
<header>
{{if .User}}
<p>Welcome, {{.User.Name}} ({{.User.Email}})</p>
{{else}}
<a href="/login">Login</a>
{{end}}
</header>
<nav>
<ul>
{{range .Menu}}
<li><a href="{{.URL}}">{{.Title}}</a></li>
{{end}}
</ul>
</nav>
<main>
{{template "content" .}}
</main>
<footer>
<p>© {{.CurrentYear}} My Company</p>
</footer>
</body>
</html>
Выполнение шаблона
// В память
err := tmpl.Execute(os.Stdout, data)
// В строку
var buf bytes.Buffer
err := tmpl.Execute(&buf, data)
result := buf.String()
// Конкретный шаблон из набора
err := tmpl.ExecuteTemplate(os.Stdout, "templateName", data)
Обработка ошибок
tmpl := template.Must(template.New("").Parse("...")) // паника при ошибке
if err := tmpl.Execute(w, data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
Безопасность
- Автоматическое экранирование HTML/JS/CSS
- Для отключения экранирования используйте типы
template.HTML,template.JS,template.CSS:
type Data struct {
SafeContent template.HTML
}
data := Data{SafeContent: template.HTML("<b>Safe HTML</b>")}
1.1.2 - Подробное руководство по define, template и block в html/template
Основные концепции
В пакете html/template есть три основные директивы для работы с повторно используемыми частями шаблонов:
{{define "name"}}...{{end}}- определяет именованный шаблон{{template "name"}}- вставляет именованный шаблон{{block "name"}}...{{end}}- комбинация define+template с возможностью переопределения
1. Директива define
Где описывать:
- В любом месте шаблона (но обычно в начале или конце файла)
- В отдельных файлах, которые потом объединяются
Пример определения:
{{define "header"}}
<header>
<h1>{{.Title}}</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
{{end}}
{{define "footer"}}
<footer>
<p>Copyright © {{.Year}} My Company</p>
</footer>
{{end}}
2. Директива template
Где применять:
- В любом месте основного шаблона
- Можно передавать данные (по умолчанию - текущий контекст)
Пример использования:
<!DOCTYPE html>
<html>
<head>
<title>{{.Title}}</title>
</head>
<body>
{{template "header" .}} <!-- Передаем текущий контекст -->
<main>
{{.Content}}
</main>
{{template "footer" .}} <!-- Передаем текущий контекст -->
</body>
</html>
Передача другого контекста:
{{template "user_profile" .UserData}}
3. Директива block
Особенности:
- Комбинация
define+template - Можно переопределять в дочерних шаблонах
- Если не переопределен - используется содержимое по умолчанию
Пример:
<!-- base.html -->
<!DOCTYPE html>
<html>
<head>
{{block "head" .}}
<title>{{.Title}} - Default</title>
{{end}}
</head>
<body>
{{block "content" .}}
<p>Default content</p>
{{end}}
</body>
</html>
Переопределение в дочернем шаблоне:
<!-- home.html -->
{{define "content"}}
<h1>Welcome!</h1>
<p>This is the home page.</p>
{{end}}
{{template "base.html" .}}
Практические схемы организации
1. Многофайловая структура
templates/
├── base.html # Основной каркас
├── header.html # Шапка сайта
├── footer.html # Подвал
├── home.html # Главная страница
└── about.html # Страница "О нас"
base.html:
<!DOCTYPE html>
<html>
<head>
{{template "head" .}}
</head>
<body>
{{template "header" .}}
{{block "content" .}}{{end}}
{{template "footer" .}}
</body>
</html>
home.html:
{{define "content"}}
<h1>Welcome</h1>
<p>Home page content</p>
{{end}}
{{template "base.html" .}}
2. Наследование шаблонов
// Загрузка шаблонов
templates := template.Must(template.ParseFiles(
"base.html",
"header.html",
"footer.html",
"home.html",
))
// Исполнение конкретного шаблона
err := templates.ExecuteTemplate(w, "home.html", data)
3. Вложенные блоки
<!-- base.html -->
{{define "layout"}}
<div class="container">
{{block "sidebar" .}}
<!-- Default sidebar -->
<div class="sidebar">Default sidebar</div>
{{end}}
<div class="content">
{{template "content" .}}
</div>
</div>
{{end}}
<!-- profile.html -->
{{define "sidebar"}}
<div class="profile-sidebar">
<img src="{{.User.Avatar}}">
<h3>{{.User.Name}}</h3>
</div>
{{end}}
{{define "content"}}
<div class="profile-content">
<!-- Контент профиля -->
</div>
{{end}}
{{template "layout" .}}
Особенности работы
- Порядок загрузки: шаблоны, на которые есть ссылки, должны быть загружены до их использования
- Контекст данных: по умолчанию передается текущий контекст, но можно передать другой
- Рекурсия: шаблоны могут вызывать сами себя (но осторожно с бесконечной рекурсией)
- Переопределение: последний загруженный шаблон с тем же именем переопределяет предыдущий
Пример с пользовательскими функциями
func main() {
funcMap := template.FuncMap{
"formatDate": func(t time.Time) string {
return t.Format("2006-01-02")
},
}
tmpl := template.Must(
template.New("base").Funcs(funcMap).ParseFiles("base.html", "home.html"),
)
err := tmpl.ExecuteTemplate(os.Stdout, "home.html", data)
}
<!-- В шаблоне: -->
<p>Created: {{.CreatedAt | formatDate}}</p>
Лучшие практики
- Разделяйте шаблоны на логические компоненты
- Используйте
blockдля переопределяемых секций - Основной каркас выносите в
base.html - Называйте шаблоны осмысленно (не “template1”, а “user_profile”)
- Документируйте назначение шаблонов в комментариях
1.1.3 - Шпаргалка по конвейерам и функциям в html/template (Go)
Синтаксис:
{{value | function1 | function2 | ...}}
Примеры:
<!-- Простой конвейер -->
<p>{{.Name | upper}}</p>
<!-- Множественные функции -->
<p>{{.Title | trim | lower | trunc 50}}</p>
<!-- С аргументами -->
<p>{{.Date | format "2006-01-02"}}</p>
<!-- Вложенные конвейеры -->
<p>{{.Description | trunc 100 | html}}</p>
Встроенные функции
1. Строковые функции
{{"hello" | title}} <!-- Hello -->
{{"HELLO" | lower}} <!-- hello -->
{{"hello" | upper}} <!-- HELLO -->
{{" hello " | trim}} <!-- hello -->
{{"hello" | repeat 3}} <!-- hellohellohello -->
2. HTML-функции
{{"<script>" | html}} <!-- Экранирование HTML -->
{{"<b>safe</b>" | html}} <!-- <b>safe</b> -->
{{"<b>safe</b>" | safeHTML}} <!-- <b>safe</b> (без экранирования) -->
3. Функции для коллекций
{{len .Items}} <!-- Длина массива/слайса/мапы -->
{{index .Users 0}} <!-- Первый элемент -->
{{index .Map "key"}} <!-- Значение по ключу -->
{{slice .List 1 3}} <!-- Подслайс -->
4. Логические функции
{{if eq .A .B}}...{{end}} <!-- Равно -->
{{if ne .A .B}}...{{end}} <!-- Не равно -->
{{if lt .A .B}}...{{end}} <!-- Меньше -->
{{if le .A .B}}...{{end}} <!-- Меньше или равно -->
{{if gt .A .B}}...{{end}} <!-- Больше -->
{{if ge .A .B}}...{{end}} <!-- Больше или равно -->
{{if and .A .B}}...{{end}} <!-- Логическое И -->
{{if or .A .B}}...{{end}} <!-- Логическое ИЛИ -->
{{if not .A}}...{{end}} <!-- Логическое НЕ -->
5. Математические функции
{{add 1 2}} <!-- 3 -->
{{sub 5 2}} <!-- 3 -->
{{mul 3 4}} <!-- 12 -->
{{div 10 2}} <!-- 5 -->
{{mod 10 3}} <!-- 1 -->
6. Функции форматирования
{{printf "%s - %d" .Name .Age}} <!-- Форматированная строка -->
{{.Price | printf "$%.2f"}} <!-- Форматирование числа -->
{{.Date | date "2006-01-02"}} <!-- Форматирование даты -->
Пользовательские функции
Регистрация функций:
funcMap := template.FuncMap{
"upper": strings.ToUpper,
"trunc": func(s string, max int) string {
if len(s) > max {
return s[:max] + "..."
}
return s
},
"add": func(a, b int) int { return a + b },
"contains": strings.Contains,
"now": time.Now,
}
tmpl := template.New("").Funcs(funcMap)
Использование в шаблоне:
{{.Title | trunc 50 | upper}}
{{add .Value 10}}
{{if contains .Description "important"}}Important!{{end}}
{{now | date "2006-01-02"}}
Примеры сложных конвейеров
1. Обработка текста
{{.UserComment | trim | trunc 200 | html}}
2. Форматирование данных
{{.CreatedAt | date "02.01.2006" | printf "Created: %s"}}
3. Работа с коллекциями
{{range .Items | sortBy "Name" | limit 10}}
{{.Name | upper}}
{{end}}
4. Условные конвейеры
{{if .Price | gt 100}}
<p class="expensive">{{.Price | printf "$%.2f"}}</p>
{{else}}
<p>{{.Price | printf "$%.2f"}}</p>
{{end}}
Полезные комбинации
Безопасный вывод HTML:
{{.UserContent | sanitizeHTML | safeHTML}}
Форматирование денежных значений:
{{.Amount | div 100 | printf "$%.2f"}}
Сортировка и фильтрация:
{{range .Products | filter "category" "electronics" | sortBy "price"}}
{{.Name}} - {{.Price | printf "$%.2f"}}
{{end}}
Ограничения
- Функции должны возвращать только одно значение (и опционально ошибку)
- Невозможно передавать функции как аргументы
- Нет поддержки сложных операций с каналами
- Автоматическое экранирование применяется к конечному результату
Лучшие практики
- Выносите сложную логику в Go-код, а не в шаблоны
- Используйте конвейеры для читаемости, но не переусердствуйте
- Документируйте пользовательские функции
- Тестируйте шаблоны с разными входными данными
- Избегайте побочных эффектов в функциях
1.1.4 - Функции экранирования HTML/JavaScript/URL пакета html/template языка Go
HTMLEscape
func HTMLEscape(w io.Writer, b []byte)
HTMLEscape записывает в w экранированный HTML-эквивалент текстовых данных b.
HTMLEscapeString
func HTMLEscapeString(s string) string
HTMLEscapeString возвращает экранированный HTML-эквивалент текстовых данных s.
HTMLEscaper
func HTMLEscaper(args ...any) string
HTMLEscaper возвращает экранированный HTML-эквивалент текстового представления своих аргументов.
IsTrue
func IsTrue(val any) (truth, ok bool)
IsTrue определяет, является ли значение “истинным” (не нулевым для своего типа) и имеет ли оно вообще смысл как булево значение. Это определение истинности используется в условных операциях типа if.
JSEscape
func JSEscape(w io.Writer, b []byte)
JSEscape записывает в w экранированный JavaScript-эквивалент текстовых данных b.
JSEscapeString
func JSEscapeString(s string) string
JSEscapeString возвращает экранированный JavaScript-эквивалент текстовых данных s.
JSEscaper
func JSEscaper(args ...any) string
JSEscaper возвращает экранированный JavaScript-эквивалент текстового представления своих аргументов.
URLQueryEscaper
func URLQueryEscaper(args ...any) string
URLQueryEscaper возвращает экранированное значение текстового представления аргументов в форме, пригодной для вставки в URL-запрос.
1.1.5 - Типы экранирования HTML/JavaScript/URL пакета html/template языка Go
type CSS
type CSS string
CSS инкапсулирует заранее проверенное содержимое, соответствующее:
- Производству CSS3 таблиц стилей (напр.
p { color: purple }) - Производству CSS3 правил (напр.
a[href=~"https:"].foo#bar) - Производству CSS3 деклараций (напр.
color: red; margin: 2px) - Производству CSS3 значений (напр.
rgba(0, 0, 255, 127))
Внимание: Использование этого типа представляет угрозу безопасности - содержимое должно поступать из доверенного источника, так как включается в вывод шаблона без изменений.
type Error
type Error struct {
ErrorCode ErrorCode // Тип ошибки
Node parse.Node // Узел, вызвавший проблему (если известен)
Name string // Имя шаблона с ошибкой
Line int // Номер строки в исходнике шаблона
Description string // Человекочитаемое описание
}
Описывает проблему, возникшую при экранировании шаблона.
Коды ошибок (ErrorCode)
const (
OK ErrorCode = iota // Нет ошибки
// Неоднозначный контекст в URL
ErrAmbigContext
// Некорректный HTML (незакрытые теги/атрибуты)
ErrBadHTML
// Ветки if/range заканчиваются в разных контекстах
ErrBranchEnd
// Шаблон заканчивается в нетекстовом контексте
ErrEndContext
// Отсутствует указанный шаблон
ErrNoSuchTemplate
// Невозможно вычислить контекст вывода
ErrOutputContext
// Незавершённый набор символов в regexp JS
ErrPartialCharset
// Незавершённая escape-последовательность
ErrPartialEscape
// Повторный вход в range с изменением контекста
ErrRangeLoopReentry
// Неоднозначная интерпретация '/' (деление или regexp)
ErrSlashAmbig
// Использование запрещённых экранировщиков
ErrPredefinedEscaper
// Устаревшая ошибка для JS template literals
ErrJSTemplate
)
Особое значение ZgotmplZ
Пример:
<img src="{{.X}}">
где {{.X}} вычисляется в javascript:...
Результат:
<img src="#ZgotmplZ">
Объяснение:
“ZgotmplZ” - специальное значение, указывающее, что небезопасное содержимое достигло CSS или URL контекста во время выполнения. Для доверенных источников используйте типы содержимого для отключения фильтрации: URL(javascript:…).
type FuncMap
type FuncMap = template.FuncMap
FuncMap - это псевдоним для типа template.FuncMap из пакета text/template, представляющий карту функций для использования в шаблонах.
type HTML
type HTML string
HTML инкапсулирует безопасный фрагмент HTML-документа. Не следует использовать для:
- HTML из ненадёжных источников
- HTML с незакрытыми тегами или комментариями
Внимание: Содержимое должно поступать из доверенного источника, так как включается в вывод шаблона без изменений.
type HTMLAttr
type HTMLAttr string
HTMLAttr инкапсулирует безопасный HTML-атрибут, например dir="ltr".
Внимание: Как и с HTML, содержимое должно быть доверенным, так как включается без экранирования.
type JS
type JS string
JS инкапсулирует безопасное выражение EcmaScript5, например (x + y * z()).
Особенности:
- Авторы шаблонов должны обеспечивать правильный порядок операций
- Не должно быть неоднозначности между выражениями и инструкциями
Внимание: Не безопасно использовать для непроверенного JSON. Для JSON лучше использовать json.Unmarshal.
type JSStr
type JSStr string
JSStr инкапсулирует безопасную строку для JavaScript, которая будет заключена в кавычки.
Требования:
- Допустимые символы или escape-последовательности
- Запрещены переносы строк внутри escape-последовательностей
Пример:
JSStr("foo\\nbar") // допустимо
JSStr("foo\\\nbar") // недопустимо
type Srcset
type Srcset string
Srcset инкапсулирует безопасный атрибут srcset для тегов <img> (спецификация HTML5).
type Template
type Template struct {
Tree *parse.Tree // Абстрактное синтаксическое дерево шаблона
// ... скрытые поля
}
Template - это специализированная версия text/template, которая генерирует безопасные фрагменты HTML-документов.
Ключевые особенности:
- Расширяет базовый функционал text/template
- Автоматически применяет контекстно-зависимое экранирование
- Гарантирует безопасность вывода при правильном использовании
Все типы безопасного контента (HTML, JS и др.) требуют, чтобы их содержимое поступало из доверенных источников, так как они включаются в вывод без дополнительного экранирования.
Пример Block
package main
import (
"html/template"
"log"
"os"
"strings"
)
func main() {
const (
master = `Names:{{block "list" .}}{{"\n"}}{{range .}}{{println "-" .}}{{end}}{{end}}`
overlay = `{{define "list"}} {{join . ", "}}{{end}} `
)
var (
funcs = template.FuncMap{"join": strings.Join}
guardians = []string{"Gamora", "Groot", "Nebula", "Rocket", "Star-Lord"}
)
masterTmpl, err := template.New("master").Funcs(funcs).Parse(master)
if err != nil {
log.Fatal(err)
}
overlayTmpl, err := template.Must(masterTmpl.Clone()).Parse(overlay)
if err != nil {
log.Fatal(err)
}
if err := masterTmpl.Execute(os.Stdout, guardians); err != nil {
log.Fatal(err)
}
if err := overlayTmpl.Execute(os.Stdout, guardians); err != nil {
log.Fatal(err)
}
}
Output:
Names:
- Gamora
- Groot
- Nebula
- Rocket
- Star-Lord
Names: Gamora, Groot, Nebula, Rocket, Star-LordПример Glob
Здесь мы демонстрируем загрузку набора шаблонов из каталога.
package main
import (
"io"
"log"
"os"
"path/filepath"
"text/template"
)
// templateFile defines the contents of a template to be stored in a file, for testing.
type templateFile struct {
name string
contents string
}
func createTestDir(files []templateFile) string {
dir, err := os.MkdirTemp("", "template")
if err != nil {
log.Fatal(err)
}
for _, file := range files {
f, err := os.Create(filepath.Join(dir, file.name))
if err != nil {
log.Fatal(err)
}
defer f.Close()
_, err = io.WriteString(f, file.contents)
if err != nil {
log.Fatal(err)
}
}
return dir
}
func main() {
// Here we create a temporary directory and populate it with our sample
// template definition files; usually the template files would already
// exist in some location known to the program.
dir := createTestDir([]templateFile{
// T0.tmpl is a plain template file that just invokes T1.
{"T0.tmpl", `T0 invokes T1: ({{template "T1"}})`},
// T1.tmpl defines a template, T1 that invokes T2.
{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
// T2.tmpl defines a template T2.
{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
})
// Clean up after the test; another quirk of running as an example.
defer os.RemoveAll(dir)
// pattern is the glob pattern used to find all the template files.
pattern := filepath.Join(dir, "*.tmpl")
// Here starts the example proper.
// T0.tmpl is the first name matched, so it becomes the starting template,
// the value returned by ParseGlob.
tmpl := template.Must(template.ParseGlob(pattern))
err := tmpl.Execute(os.Stdout, nil)
if err != nil {
log.Fatalf("template execution: %s", err)
}
}
Output:
T0 invokes T1: (T1 invokes T2: (This is T2))
Пример Helpers
Этот пример демонстрирует один из способов поделиться некоторыми шаблонами и использовать их в разных контекстах. В этом варианте мы вручную добавляем несколько шаблонов драйверов в существующий пакет шаблонов.
package main
import (
"io"
"log"
"os"
"path/filepath"
"text/template"
)
// templateFile defines the contents of a template to be stored in a file, for testing.
type templateFile struct {
name string
contents string
}
func createTestDir(files []templateFile) string {
dir, err := os.MkdirTemp("", "template")
if err != nil {
log.Fatal(err)
}
for _, file := range files {
f, err := os.Create(filepath.Join(dir, file.name))
if err != nil {
log.Fatal(err)
}
defer f.Close()
_, err = io.WriteString(f, file.contents)
if err != nil {
log.Fatal(err)
}
}
return dir
}
func main() {
// Here we create a temporary directory and populate it with our sample
// template definition files; usually the template files would already
// exist in some location known to the program.
dir := createTestDir([]templateFile{
// T1.tmpl defines a template, T1 that invokes T2.
{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
// T2.tmpl defines a template T2.
{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
})
// Clean up after the test; another quirk of running as an example.
defer os.RemoveAll(dir)
// pattern is the glob pattern used to find all the template files.
pattern := filepath.Join(dir, "*.tmpl")
// Here starts the example proper.
// Load the helpers.
templates := template.Must(template.ParseGlob(pattern))
// Add one driver template to the bunch; we do this with an explicit template definition.
_, err := templates.Parse("{{define `driver1`}}Driver 1 calls T1: ({{template `T1`}})\n{{end}}")
if err != nil {
log.Fatal("parsing driver1: ", err)
}
// Add another driver template.
_, err = templates.Parse("{{define `driver2`}}Driver 2 calls T2: ({{template `T2`}})\n{{end}}")
if err != nil {
log.Fatal("parsing driver2: ", err)
}
// We load all the templates before execution. This package does not require
// that behavior but html/template's escaping does, so it's a good habit.
err = templates.ExecuteTemplate(os.Stdout, "driver1", nil)
if err != nil {
log.Fatalf("driver1 execution: %s", err)
}
err = templates.ExecuteTemplate(os.Stdout, "driver2", nil)
if err != nil {
log.Fatalf("driver2 execution: %s", err)
}
}
Output:
Driver 1 calls T1: (T1 invokes T2: (This is T2))
Driver 2 calls T2: (This is T2)
Пример Parsefiles
Здесь мы демонстрируем загрузку набора шаблонов из файлов в разных каталогах.
package main
import (
"io"
"log"
"os"
"path/filepath"
"text/template"
)
// templateFile defines the contents of a template to be stored in a file, for testing.
type templateFile struct {
name string
contents string
}
func createTestDir(files []templateFile) string {
dir, err := os.MkdirTemp("", "template")
if err != nil {
log.Fatal(err)
}
for _, file := range files {
f, err := os.Create(filepath.Join(dir, file.name))
if err != nil {
log.Fatal(err)
}
defer f.Close()
_, err = io.WriteString(f, file.contents)
if err != nil {
log.Fatal(err)
}
}
return dir
}
func main() {
// Here we create different temporary directories and populate them with our sample
// template definition files; usually the template files would already
// exist in some location known to the program.
dir1 := createTestDir([]templateFile{
// T1.tmpl is a plain template file that just invokes T2.
{"T1.tmpl", `T1 invokes T2: ({{template "T2"}})`},
})
dir2 := createTestDir([]templateFile{
// T2.tmpl defines a template T2.
{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
})
// Clean up after the test; another quirk of running as an example.
defer func(dirs ...string) {
for _, dir := range dirs {
os.RemoveAll(dir)
}
}(dir1, dir2)
// Here starts the example proper.
// Let's just parse only dir1/T0 and dir2/T2
paths := []string{
filepath.Join(dir1, "T1.tmpl"),
filepath.Join(dir2, "T2.tmpl"),
}
tmpl := template.Must(template.ParseFiles(paths...))
err := tmpl.Execute(os.Stdout, nil)
if err != nil {
log.Fatalf("template execution: %s", err)
}
}
Output:
T1 invokes T2: (This is T2)Пример Share
В этом примере показано, как использовать одну группу шаблонов драйверов с различными наборами вспомогательных шаблонов.
package main
import (
"io"
"log"
"os"
"path/filepath"
"text/template"
)
// templateFile defines the contents of a template to be stored in a file, for testing.
type templateFile struct {
name string
contents string
}
func createTestDir(files []templateFile) string {
dir, err := os.MkdirTemp("", "template")
if err != nil {
log.Fatal(err)
}
for _, file := range files {
f, err := os.Create(filepath.Join(dir, file.name))
if err != nil {
log.Fatal(err)
}
defer f.Close()
_, err = io.WriteString(f, file.contents)
if err != nil {
log.Fatal(err)
}
}
return dir
}
func main() {
// Here we create a temporary directory and populate it with our sample
// template definition files; usually the template files would already
// exist in some location known to the program.
dir := createTestDir([]templateFile{
// T0.tmpl is a plain template file that just invokes T1.
{"T0.tmpl", "T0 ({{.}} version) invokes T1: ({{template `T1`}})\n"},
// T1.tmpl defines a template, T1 that invokes T2. Note T2 is not defined
{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
})
// Clean up after the test; another quirk of running as an example.
defer os.RemoveAll(dir)
// pattern is the glob pattern used to find all the template files.
pattern := filepath.Join(dir, "*.tmpl")
// Here starts the example proper.
// Load the drivers.
drivers := template.Must(template.ParseGlob(pattern))
// We must define an implementation of the T2 template. First we clone
// the drivers, then add a definition of T2 to the template name space.
// 1. Clone the helper set to create a new name space from which to run them.
first, err := drivers.Clone()
if err != nil {
log.Fatal("cloning helpers: ", err)
}
// 2. Define T2, version A, and parse it.
_, err = first.Parse("{{define `T2`}}T2, version A{{end}}")
if err != nil {
log.Fatal("parsing T2: ", err)
}
// Now repeat the whole thing, using a different version of T2.
// 1. Clone the drivers.
second, err := drivers.Clone()
if err != nil {
log.Fatal("cloning drivers: ", err)
}
// 2. Define T2, version B, and parse it.
_, err = second.Parse("{{define `T2`}}T2, version B{{end}}")
if err != nil {
log.Fatal("parsing T2: ", err)
}
// Execute the templates in the reverse order to verify the
// first is unaffected by the second.
err = second.ExecuteTemplate(os.Stdout, "T0.tmpl", "second")
if err != nil {
log.Fatalf("second execution: %s", err)
}
err = first.ExecuteTemplate(os.Stdout, "T0.tmpl", "first")
if err != nil {
log.Fatalf("first: execution: %s", err)
}
}
Output:
T0 (second version) invokes T1: (T1 invokes T2: (T2, version B))
T0 (first version) invokes T1: (T1 invokes T2: (T2, version A))
func Must
func Must(t *Template, err error) *Template
Must - вспомогательная функция, которая оборачивает вызов функции, возвращающей (*Template, error), и вызывает панику, если ошибка не равна nil. Предназначена для инициализации переменных:
Пример:
var t = template.Must(template.New("name").Parse("html"))
func New
func New(name string) *Template
New создает новый HTML-шаблон с указанным именем.
func ParseFS (добавлена в Go 1.16)
func ParseFS(fs fs.FS, patterns ...string) (*Template, error)
ParseFS аналогична ParseFiles и ParseGlob, но читает из файловой системы fs вместо файловой системы хоста. Принимает список glob-шаблонов (большинство имен файлов работают как glob-шаблоны, соответствующие только самим себе).
func ParseFiles
func ParseFiles(filenames ...string) (*Template, error)
ParseFiles создает новый шаблон и парсит определения шаблонов из указанных файлов. Имя возвращаемого шаблона будет соответствовать базовому имени первого файла. Должен быть указан хотя бы один файл.
Особенности:
- При парсинге нескольких файлов с одинаковыми именами будет использован последний указанный файл
- Например,
ParseFiles("a/foo", "b/foo")сохранит “b/foo” как шаблон “foo”, а “a/foo” будет недоступен
func ParseGlob
func ParseGlob(pattern string) (*Template, error)
ParseGlob создает новый шаблон и парсит определения шаблонов из файлов, соответствующих указанному шаблону (согласно семантике filepath.Match). Шаблон должен соответствовать хотя бы одному файлу.
Эквивалентно вызову ParseFiles со списком файлов, соответствующих шаблону. Как и в ParseFiles, при дублировании имен используется последний файл.
func (*Template) AddParseTree
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error)
AddParseTree создает новый шаблон с указанным именем и деревом разбора, ассоциируя его с t.
Возвращает ошибку, если t или любой ассоциированный шаблон уже были выполнены.
func (*Template) Clone
func (t *Template) Clone() (*Template, error)
Clone создает копию шаблона, включая все ассоциированные шаблоны. Последующие вызовы Parse в копии будут добавлять шаблоны только в копию.
Возвращает ошибку, если t уже был выполнен.
func (*Template) DefinedTemplates (добавлена в Go 1.6)
func (t *Template) DefinedTemplates() string
DefinedTemplates возвращает строку с перечислением определенных шаблонов с префиксом “; defined templates are: “. Если шаблонов нет, возвращает пустую строку. Используется для генерации сообщений об ошибках.
func (*Template) Delims
func (t *Template) Delims(left, right string) *Template
Delims устанавливает разделители действий для последующих вызовов Parse, ParseFiles или ParseGlob. Вложенные определения шаблонов наследуют эти настройки. Пустые разделители соответствуют значениям по умолчанию: {{ и }}.
Возвращает сам шаблон, позволяя объединять вызовы в цепочку.
Пример
package main
import (
"html/template"
"log"
"os"
)
func main() {
const text = "<<.Greeting>> {{.Name}}"
data := struct {
Greeting string
Name string
}{
Greeting: "Hello",
Name: "Joe",
}
t := template.Must(template.New("tpl").Delims("<<", ">>").Parse(text))
err := t.Execute(os.Stdout, data)
if err != nil {
log.Fatal(err)
}
}
Output:
Hello {{.Name}}func (*Template) Execute
func (t *Template) Execute(wr io.Writer, data any) error
Применяет разобранный шаблон к указанным данным, записывая результат в wr. В случае ошибки выполнение прерывается, но частичные результаты могут быть уже записаны. Шаблон можно выполнять параллельно, но при использовании общего Writer вывод может перемешиваться.
func (*Template) ExecuteTemplate
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data any) error
Применяет шаблон с указанным именем, ассоциированный с t, к данным и записывает результат в wr. Поведение при ошибках и параллельном выполнении аналогично Execute.
Отличие Execute от ExecuteTemplate
Отличие между Execute и ExecuteTemplate
Основное различие между этими методами заключается в том, какой шаблон они выполняют:
Execute- выполняет основной (корневой) шаблонExecuteTemplate- выполняет указанный именованный шаблон (включая ассоциированные шаблоны)
Пример использования:
package main
import (
"os"
"text/template"
)
func main() {
// Создаем шаблон с именем "base"
tmpl := template.Must(template.New("base").Parse(`
{{define "header"}}<h1>Заголовок</h1>{{end}}
{{define "content"}}<p>Основное содержимое</p>{{end}}
<!DOCTYPE html>
<html>
<head><title>Пример</title></head>
<body>
{{template "header"}}
{{template "content"}}
</body>
</html>
`))
// 1. Execute - выполнит основной шаблон "base"
err := tmpl.Execute(os.Stdout, nil)
if err != nil {
panic(err)
}
// 2. ExecuteTemplate - можно выполнить любой из определенных шаблонов
err = tmpl.ExecuteTemplate(os.Stdout, "header", nil)
if err != nil {
panic(err)
}
}
Ключевые отличия:
| Характеристика | Execute | ExecuteTemplate |
|---|---|---|
| Какой шаблон выполняется | Только основной (корневой) | Любой именованный (включая основной) |
| Использование | Когда нужно выполнить весь шаблон | Когда нужна часть шаблона |
| Производительность | Чуть быстрее | Чуть медленнее (поиск по имени) |
| Гибкость | Меньше | Больше (доступ ко всем шаблонам) |
Когда использовать:
Execute- для рендеринга всего шаблона целикомExecuteTemplate- когда нужно:- выполнить часть шаблона
- переиспользовать компоненты в разных контекстах
- отладить отдельный блок шаблона
func (*Template) Funcs
func (t *Template) Funcs(funcMap FuncMap) *Template
Добавляет функции из переданной мапы в функциональную мапу шаблона. Должен вызываться до парсинга шаблона. Вызывает панику, если значение в мапе не является функцией с подходящим типом возвращаемого значения. Возвращает сам шаблон для цепочки вызовов.
func (*Template) Lookup
func (t *Template) Lookup(name string) *Template
Возвращает шаблон с указанным именем, ассоциированный с t, или nil если такого шаблона нет.
func (*Template) Name
func (t *Template) Name() string
Возвращает имя шаблона.
func (*Template) New
func (t *Template) New(name string) *Template
Создает новый HTML-шаблон с указанным именем, ассоциированный с текущим и имеющий те же разделители. Позволяет использовать вызовы шаблонов через {{template}}. Если шаблон с таким именем уже существует, он будет заменен.
func (*Template) Option
func (t *Template) Option(opt ...string) *Template
Устанавливает опции шаблона в формате строк “ключ=значение” или простых строк. Вызывает панику при неизвестной или неверной опции.
Поддерживаемые опции:
missingkey=defaultилиmissingkey=invalid- поведение по умолчанию (продолжать выполнение)missingkey=zero- возвращать нулевое значение для отсутствующего ключаmissingkey=error- прерывать выполнение с ошибкой
func (*Template) Parse
func (t *Template) Parse(text string) (*Template, error)
Разбирает текст как тело шаблона для t. Определения именованных шаблонов ({{define}}/{{block}}) становятся ассоциированными шаблонами. Шаблоны можно переопределять до первого вызова Execute. Пустые определения (только пробелы/комментарии) не заменяют существующие шаблоны.
func (*Template) ParseFS
func (t *Template) ParseFS(fs fs.FS, patterns ...string) (*Template, error)
ParseFS аналогична Template.ParseFiles и Template.ParseGlob, но читает из абстрактной файловой системы fs вместо файловой системы хоста. Принимает список glob-шаблонов (большинство имен файлов работают как glob-шаблоны, соответствующие только самим себе).
func (*Template) ParseFiles
func (t *Template) ParseFiles(filenames ...string) (*Template, error)
ParseFiles парсит указанные файлы и ассоциирует полученные шаблоны с t. При ошибке парсинг останавливается и возвращается nil, иначе возвращается t. Должен быть указан хотя бы один файл.
Особенности:
- При парсинге нескольких файлов с одинаковыми именами используется последний указанный файл
- Возвращает ошибку, если t или любой ассоциированный шаблон уже были выполнены
func (*Template) ParseGlob
func (t *Template) ParseGlob(pattern string) (*Template, error)
ParseGlob парсит шаблоны из файлов, соответствующих указанному шаблону (согласно семантике filepath.Match), и ассоциирует их с t. Эквивалентно вызову t.ParseFiles со списком файлов, соответствующих шаблону.
Особенности:
- Как и в ParseFiles, при дублировании имен используется последний файл
- Возвращает ошибку, если t или любой ассоциированный шаблон уже были выполнены
func (*Template) Templates
func (t *Template) Templates() []*Template
Templates возвращает срез всех шаблонов, ассоциированных с t, включая сам t.
type URL
type URL string
URL инкапсулирует безопасный URL или подстроку URL (согласно RFC 3986). URL типа javascript:checkThatFormNotEditedBeforeLeavingPage() из доверенного источника может быть использован, но по умолчанию динамические javascript: URL фильтруются как частый вектор атак.
Внимание: Использование этого типа представляет угрозу безопасности - содержимое должно поступать из доверенного источника, так как включается в вывод шаблона без изменений.
2 - Пакет bufio встроенных функций Go
Объяснение bufio
Пакет bufio предоставляет буферизованный ввод/вывод, что делает работу с потоками данных более эффективной. Он особенно полезен для:
- Чтения больших файлов по частям
- Обработки текстовых данных построчно
- Разбивки входных данных на слова, символы и другие логические единицы
Почему использовать bufio?
- Эффективность: Буферизация уменьшает количество системных вызовов
- Удобство: Готовые функции для распространенных задач (чтение строк, слов)
- Гибкость: Можно создавать собственные функции разделения
- Производительность: Особенно заметна при работе с большими файлами
Пример комплексного использования:
file, _ := os.Open("data.txt")
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanWords) // Разбиваем по словам
wordCount := 0
for scanner.Scan() {
wordCount++
}
fmt.Printf("Файл содержит %d слов", wordCount)
Пакет bufio - это мощный инструмент для эффективной обработки текстовых и бинарных данных в Go, который стоит использовать вместо базовых операций ввода-вывода, когда важны производительность и удобство работы.
Константы
const (
// maxscantokensize - максимальный размер, используемый для буфера токена
// Если пользователь не предоставит явный буфер с [scanner.buffer].
// фактический максимальный размер токена может быть меньше в качестве буфера
// может потребоваться включить, например, новую линию.
Maxscantokensize = 64 * 1024
)
Переменные
var (
ErrinValidunReadByte = errors.new ("Bufio: недействительное использование UnReadByte")
Errinvalidunreadrune = errors.new ("bufio: недействительное использование Undeardrune")
Errbufferfull = errors.new ("bufio: buffer full")
Errnegativecount = errors.new ("bufio: отрицательный счет")
)
var (
Errtoolong = errors.new ("bufio.scanner: токен слишком длинный")
Errnegativeadvance = errors.new ("bufio.scanner: splitfunc возвращает отрицательное значение сдвига") ")
ErradvancetOfar = errors.new ("bufio.scanner: splitfunc возвращает сдвиг, превышающий входные данные")
ErrbadReadCount = errors.new ("bufio.scanner: read возвращается невозможное количество")
)
Ошибки возвращаемые Scanner.
var errfinaltoken = errors.new ("Окончательный токен")
ErrFinalToken — это специальное контрольное значение ошибки. Оно предназначено для возврата функцией Split, чтобы указать, что сканирование должно быть остановлено без ошибки. Если токен, передаваемый с этой ошибкой, не равен nil, то этот токен является последним токеном.
Это значение полезно для досрочного прекращения обработки или когда необходимо передать последний пустой токен (который отличается от токена nil). Такое же поведение можно достичь с помощью пользовательского значения ошибки, но использование этого значения более удобно. Пример использования этого значения см. в примере emptyFinalToken.
Функции
func ScanBytes
func ScanBytes(data []byte, atEOF bool) (advance int, token []byte, err error)
ScanBytes — это функция разделения для сканера, которая возвращает каждый байт в виде токена.
Назначение: Чтение входных данных побайтово.
Пример:
scanner := bufio.NewScanner(strings.NewReader("Go"))
scanner.Split(bufio.ScanBytes)
for scanner.Scan() {
fmt.Printf("%q ", scanner.Bytes())
}
// Вывод: 'G' 'o'
Когда использовать:
- Когда нужно обрабатывать каждый байт данных
- Для низкоуровневой обработки бинарных данных
func ScanLines
func ScanLines(data []byte, atEOF bool) (advance int, token []byte, err error)
ScanLines — это функция разделения для сканера, которая возвращает каждую строку текста без конечного маркера конца строки. Возвращаемая строка может быть пустой. Маркер конца строки — это один необязательный возврат каретки, за которым следует один обязательный символ новой строки. В нотации регулярных выражений это \r?\n. Последняя непустая строка ввода будет возвращена, даже если в ней нет символа новой строки.
Назначение: Чтение входных данных построчно (по умолчанию в Scanner).
Пример:
scanner := bufio.NewScanner(strings.NewReader("line1\nline2\n"))
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
// Вывод:
// line1
// line2
Когда использовать:
- Для обработки логов
- Чтения конфигурационных файлов
- Обработки любого текста, организованного по строкам
func ScanRunes
func ScanRunes(data []byte, atEOF bool) (advance int, token []byte, err error)
ScanRunes — это функция разделения для сканера, которая возвращает каждую руну, закодированную в UTF-8, в виде токена. Последовательность возвращаемых рун эквивалентна последовательности из цикла range над входом в виде строки, что означает, что ошибочные кодировки UTF-8 переводятся в U+FFFD = “\xef\xbf\xbd”. Из-за интерфейса Scan это делает невозможным для клиента отличить правильно закодированные руны-замены от ошибок кодирования.
Назначение: Чтение входных данных посимвольно (с поддержкой UTF-8).
Пример:
scanner := bufio.NewScanner(strings.NewReader("Привет"))
scanner.Split(bufio.ScanRunes)
for scanner.Scan() {
fmt.Printf("%q ", scanner.Text())
}
// Вывод: 'П' 'р' 'и' 'в' 'е' 'т'
Когда использовать:
- Для обработки Unicode-текстов
- Когда нужно работать с отдельными символами (рунами)
- Для анализа текста на уровне символов
func ScanWords
func ScanWords(data []byte, atEOF bool) (advance int, token []byte, err error)
ScanWords — это функция разделения для Scanner, которая возвращает каждое слово текста, разделенное пробелом, с удалением окружающих пробелов. Она никогда не возвращает пустую строку. Определение пробела устанавливается unicode.IsSpace.
Назначение: Чтение входных данных по словам.
Пример:
scanner := bufio.NewScanner(strings.NewReader("Hello world!"))
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
// Вывод:
// Hello
// world!
Когда использовать:
- Для анализа текста по словам
- При обработке естественного языка
- Для подсчета слов в тексте
Типы
type ReadWriter
type ReadWriter struct {
*Reader
*Writer
}
ReadWriter хранит указатели на Reader и Writer. Он реализует io.ReadWriter.
func NewReadWriter
func NewReadWriter(r *Reader, w *Writer) *ReadWriter
NewReadWriter выделяет новый ReadWriter, который отправляет данные в R и W.
type Reader
type Reader struct {
// contains filtered or unexported fields
}
Reader реализует буферизацию для объекта io.Reader. Новый Reader создается вызовом NewReader или NewReaderSize; в качестве альтернативы можно использовать нулевое значение Reader после вызова [Reset] для него.
Объяснение отличий Scanner, Reader и Writer
Сравнение Scanner, Reader и Writer в пакете bufio
Основные различия
1. Reader (буферизованное чтение)
Что делает:
- Читает данные порциями в буфер
- Предоставляет низкоуровневые методы (
Read,ReadByte,ReadLineи др.) - Требует ручного управления процессом чтения
Когда использовать:
reader := bufio.NewReader(file)
// Чтение точного количества байт
buf := make([]byte, 1024)
n, err := reader.Read(buf)
// Чтение до разделителя
line, err := reader.ReadString('\n')
Лучше использовать когда:
- Нужен точный контроль над процессом чтения
- Требуется чтение данных фиксированного размера
- Работа с бинарными данными
- Когда нужно несколько раз переиспользовать один и тот же буфер
2. Scanner (высокоуровневое сканирование)
Что делает:
- Автоматически разбивает входные данные на токены
- Предоставляет простой итерационный интерфейс
- Имеет встроенные функции для разделения на строки, слова, руны
Когда использовать:
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text()) // автоматическое чтение строк
}
Лучше использовать когда:
- Обработка текстовых данных построчно
- Нужно разделять входные данные по сложным правилам
- Работа с текстовыми форматами (логи, CSV и т.д.)
- Когда важна простота кода
3. Writer (буферизованная запись)
Что делает:
- Накапливает данные в буфере перед записью
- Предоставляет методы для эффективной записи (
Write,WriteString,WriteByte)
Когда использовать:
writer := bufio.NewWriter(file)
writer.WriteString("Hello, ")
writer.WriteString("World!\n")
writer.Flush() // запись из буфера в файл
Сравнительная таблица
| Характеристика | Reader | Scanner | Writer |
|---|---|---|---|
| Уровень абстракции | Низкий | Высокий | Средний |
| Управление буфером | Ручное | Автоматическое | Полуавтоматическое |
| Чтение строк | ReadString(’\n') | Scan() + Text() | - |
| Производительность | Очень высокая | Высокая | Высокая |
| Удобство использования | Среднее | Очень высокое | Высокое |
| Лучший случай использования | Бинарные данные, сети | Текстовые файлы, логи | Запись в файлы, сети |
Практические рекомендации
Используйте
Scannerкогда:- Обрабатываете текстовые файлы построчно
- Нужно разделять данные по словам/символам
- Хотите простой и читаемый код
// Подсчет слов в файле scanner := bufio.NewScanner(file) scanner.Split(bufio.ScanWords) count := 0 for scanner.Scan() { count++ }Используйте
Readerкогда:- Работаете с бинарными данными
- Нужно читать данные точными порциями
- Требуется низкоуровневый контроль
// Чтение заголовка файла reader := bufio.NewReader(file) header := make([]byte, 16) _, err := reader.Read(header)Используйте
Writerкогда:- Нужно эффективно записывать много небольших фрагментов
- Важна производительность при записи
// Буферизованная запись writer := bufio.NewWriter(file) for i := 0; i < 1000; i++ { writer.WriteString(fmt.Sprintf("Line %d\n", i)) } writer.Flush()
Производительность
- Для больших файлов
Readerобычно быстрееScanner - Для текстовой обработки
Scannerудобнее и часто достаточно быстр Writerдает значительный прирост производительности при множественных мелких записях
Заключение
Выбор между Scanner, Reader и Writer зависит от конкретной задачи:
- Простота и удобство →
Scanner - Контроль и производительность →
Reader/Writer - Текстовые данные →
Scanner - Бинарные данные →
Reader - Эффективная запись →
Writer
func NewReader
func NewReader(rd io.Reader) *Reader
NewReader возвращает новый объект Reader, буфер которого имеет размер по умолчанию.
func NewReaderSize
func NewReaderSize(rd io.Reader, size int) *Reader
NewReaderSize возвращает новый Reader, буфер которого имеет как минимум указанный размер. Если аргумент io.Reader уже является Reader с достаточно большим размером, то возвращается базовый Reader.
func (*Reader) Buffered
func (b *Reader) Buffered() int
Buffered возвращает количество байтов, которые можно прочитать из текущего буфера.
func (*Reader) Discard
added in go1.5
func (b *Reader) Discard(n int) (discarded int, err error)
Discard пропускает следующие n байтов и возвращает количество пропущенных байтов.
Если Discard пропускает меньше n байтов, он также возвращает ошибку. Если 0 <= n <= b.Buffered(), Discard гарантированно выполнится успешно без чтения из базового io.Reader.
func (*Reader) Peek
func (b *Reader) Peek(n int) ([]byte, error)
Peek возвращает следующие n байтов, не продвигая считывающее устройство. Байты перестают быть действительными при следующем вызове чтения. При необходимости Peek прочитает больше байтов в буфер, чтобы сделать доступными n байтов. Если Peek возвращает меньше n байтов, он также возвращает ошибку, объясняющую, почему чтение является коротким. Ошибка ErrBufferFull возникает, если n больше размера буфера b.
Вызов Peek предотвращает выполнение вызова Reader.UnreadByte или Reader.UnreadRune до следующей операции чтения.
func (*Reader) Read
func (b *Reader) Read(p []byte) (n int, err error)
Read считывает данные в p. Он возвращает количество байтов, прочитанных в p. Байты берутся максимум из одного Read в базовом Reader, поэтому n может быть меньше len(p). Чтобы прочитать ровно len(p) байтов, используйте io.ReadFull(b, p). Если базовый Reader может возвращать ненулевое значение с io.EOF, то этот метод Read также может это делать; см. документацию io.Reader.
func (*Reader) ReadByte
func (b *Reader) ReadByte() (byte, error)
ReadByte считывает и возвращает один байт. Если байт недоступен, возвращает ошибку.
func (*Reader) ReadBytes
func (b *Reader) ReadBytes(delim byte) ([]byte, error)
ReadBytes читает входные данные до первого появления разделителя delim, возвращая фрагмент, содержащий данные до разделителя включительно. Если ReadBytes обнаруживает ошибку до нахождения разделителя, он возвращает данные, прочитанные до ошибки, и саму ошибку (часто io.EOF). ReadBytes возвращает err != nil, если и только если возвращаемые данные не заканчиваются на delim. Для простых задач может быть более удобно использовать Scanner.
func (*Reader) ReadLine
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)
ReadLine — это низкоуровневая примитивная функция чтения строк. Большинству вызывающих функций следует использовать Reader.ReadBytes(’\n’) или Reader.ReadString(’\n’) вместо нее, либо использовать Scanner.
ReadLine пытается вернуть одну строку, не включая байты конца строки. Если строка была слишком длинной для буфера, то устанавливается isPrefix и возвращается начало строки. Остальная часть строки будет возвращена при последующих вызовах. isPrefix будет false при возвращении последнего фрагмента строки. Возвращаемый буфер действителен только до следующего вызова ReadLine. ReadLine либо возвращает строку, отличную от nil, либо возвращает ошибку, но никогда и то, и другое одновременно.
Текст, возвращаемый из ReadLine, не включает конец строки («\r\n» или «\n»). Если ввод заканчивается без конечного конца строки, никаких указаний или ошибок не выдается. Вызов Reader.UnreadByte после ReadLine всегда отменяет чтение последнего прочитанного байта (возможно, символа, принадлежащего концу строки), даже если этот байт не является частью строки, возвращаемой ReadLine.
func (*Reader) ReadRune
func (b *Reader) ReadRune() (r rune, size int, err error)
ReadRune считывает один символ Unicode, закодированный в UTF-8, и возвращает руну и ее размер в байтах. Если закодированная руна недействительна, она потребляет один байт и возвращает unicode.ReplacementChar (U+FFFD) с размером 1.
func (*Reader) ReadSlice
func (b *Reader) ReadSlice(delim byte) (line []byte, err error)
ReadSlice читает до первого появления разделителя в входных данных, возвращая фрагмент, указывающий на байты в буфере. Байты перестают быть действительными при следующем чтении. Если ReadSlice встречает ошибку до нахождения разделителя, он возвращает все данные в буфере и саму ошибку (часто io.EOF). ReadSlice завершается с ошибкой ErrBufferFull, если буфер заполняется без delim. Поскольку данные, возвращаемые из ReadSlice, будут перезаписаны следующей операцией ввода-вывода, большинство клиентов должны использовать Reader.ReadBytes или ReadString вместо этого. ReadSlice возвращает err != nil, если и только если строка не заканчивается на delim.
func (*Reader) ReadString
func (b *Reader) ReadString(delim byte) (string, error)
ReadString читает входные данные до первого появления delim, возвращая строку, содержащую данные до разделителя включительно. Если ReadString встречает ошибку до нахождения разделителя, он возвращает данные, прочитанные до ошибки, и саму ошибку (часто io.EOF). ReadString возвращает err != nil тогда и только тогда, когда возвращаемые данные не заканчиваются на delim. Для простых задач может быть более удобно использовать Scanner.
func (*Reader) Reset
func (b *Reader) Reset(r io.Reader)
Reset удаляет все данные из буфера, сбрасывает все состояния и переключает буферизованный считыватель на чтение из r. Вызов Reset с нулевым значением Reader инициализирует внутренний буфер до размера по умолчанию. Вызов b.Reset(b) (то есть сброс Reader на себя) ничего не делает.
func (*Reader) Size
func (b *Reader) Size() int
Size возвращает размер базового буфера в байтах.
func (*Reader) UnreadByte
func (b *Reader) UnreadByte() error
UnreadByte отменяет чтение последнего байта. Отменить чтение можно только последнего прочитанного байта.
UnreadByte возвращает ошибку, если последний вызов метода Reader не был операцией чтения. В частности, методы Reader.Peek, Reader.Discard и Reader.WriteTo не считаются операциями чтения.
func (*Reader) UnreadRune
func (b *Reader) UnreadRune() error
UnreadRune снимает отметку о прочтении с последней руны. Если последний вызов метода Reader не был Reader.ReadRune, Reader.UnreadRune возвращает ошибку. (В этом отношении он более строг, чем Reader.UnreadByte, который снимает отметку о прочтении с последнего байта любой операции чтения.)
func (*Reader) WriteTo
func (b *Reader) WriteTo(w io.Writer) (n int64, err error)
WriteTo реализует io.WriterTo. Это может привести к нескольким вызовам метода Reader.Read базового Reader. Если базовый Reader поддерживает метод Reader.WriteTo, то вызывается базовый Reader.WriteTo без буферизации.
type Scanner
type Scanner struct {
// contains filtered or unexported fields
}
Scanner предоставляет удобный интерфейс для чтения данных, таких как файл текста, разбитый на строки с разделителями новой строки. Последовательные вызовы метода Scanner.Scan будут проходить по «токену» файла, пропуская байты между токенами. Спецификация токена определяется функцией разбиения типа SplitFunc; функция разбиения по умолчанию разбивает входные данные на строки с удалением символов окончания строки. Функции Scanner.Split определены в этом пакете для сканирования файла на строки, байты, руны с кодировкой UTF-8 и слова, разделенные пробелами. Клиент может вместо этого предоставить собственную функцию разделения.
Сканирование останавливается без возможности восстановления при EOF, первой ошибке ввода-вывода или токене, который слишком велик, чтобы поместиться в Scanner.Buffer. Когда сканирование останавливается, читатель может продвинуться произвольно далеко за последний токен. Программы, которым требуется больший контроль над обработкой ошибок или большими токенами, или которые должны выполнять последовательное сканирование на читателе, должны использовать bufio.Reader.
Объяснение Scanner
Тип Scanner из пакета bufio в Go
Scanner предоставляет удобный интерфейс для чтения и разбора текстовых данных. Это один из самых полезных инструментов для обработки текста в Go.
Основные возможности
- Чтение данных по частям (строкам, словам, символам и т.д.)
- Гибкое разделение входных данных (можно использовать готовые или свои функции)
- Эффективная буферизация (работает быстро даже с большими файлами)
Создание Scanner
scanner := bufio.NewScanner(reader)
где reader - это любой объект, реализующий интерфейс io.Reader (файл, строка, сетевое соединение и т.д.)
Основные методы
1. Scan() bool
Продвигает сканер к следующему токену. Возвращает false, когда сканирование завершено.
for scanner.Scan() {
// Обработка текущего токена
}
2. Text() string
Возвращает текущий токен как строку.
scanner := bufio.NewScanner(strings.NewReader("Hello\nWorld"))
for scanner.Scan() {
fmt.Println(scanner.Text())
}
// Вывод:
// Hello
// World
3. Bytes() []byte
Возвращает текущий токен как срез байтов.
scanner := bufio.NewScanner(strings.NewReader("Hello"))
scanner.Scan()
fmt.Printf("%q", scanner.Bytes()) // Вывод: "Hello"
4. Err() error
Возвращает первую ошибку, возникшую при сканировании.
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
5. Split(splitFunc SplitFunc)
Устанавливает функцию разделения. По умолчанию используется ScanLines.
scanner := bufio.NewScanner(strings.NewReader("Hello World"))
scanner.Split(bufio.ScanWords) // Разделять по словам
Стандартные функции разделения
ScanLines (по умолчанию)
scanner := bufio.NewScanner(strings.NewReader("line1\nline2\nline3"))
for scanner.Scan() {
fmt.Println(scanner.Text())
}
ScanWords
scanner := bufio.NewScanner(strings.NewReader("word1 word2 word3"))
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
ScanRunes
scanner := bufio.NewScanner(strings.NewReader("Привет"))
scanner.Split(bufio.ScanRunes)
for scanner.Scan() {
fmt.Printf("%q ", scanner.Text())
}
// Вывод: 'П' 'р' 'и' 'в' 'е' 'т'
ScanBytes
scanner := bufio.NewScanner(strings.NewReader("Go"))
scanner.Split(bufio.ScanBytes)
for scanner.Scan() {
fmt.Printf("%q ", scanner.Bytes())
}
// Вывод: 'G' 'o'
Пользовательская функция разделения
Вы можете создать свою функцию разделения:
func atComma(data []byte, atEOF bool) (advance int, token []byte, err error) {
for i := 0; i < len(data); i++ {
if data[i] == ',' {
return i + 1, data[:i], nil
}
}
return 0, data, bufio.ErrFinalToken
}
func main() {
scanner := bufio.NewScanner(strings.NewReader("a,b,c,d"))
scanner.Split(atComma)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
// Вывод:
// a
// b
// c
// d
Полезные советы
Ограничение размера буфера:
scanner := bufio.NewScanner(reader) buf := make([]byte, 0, 64*1024) scanner.Buffer(buf, 1024*1024) // Максимальный размер токена 1MBОбработка больших строк:
scanner := bufio.NewScanner(file) scanner.Buffer(make([]byte, 100), bufio.MaxScanTokenSize)Подсчет строк в файле:
file, _ := os.Open("data.txt") defer file.Close() scanner := bufio.NewScanner(file) lineCount := 0 for scanner.Scan() { lineCount++ }
Scanner идеально подходит для:
- Обработки логов
- Чтения конфигурационных файлов
- Анализа текстовых данных
- Разбора CSV и других текстовых форматов
Главное преимущество - это простота использования и эффективность при работе с большими объемами данных.
Пример пользовательский
Используйте сканер с настраиваемой функцией разделения (созданный с помощью ScanWords) для проверки 32-разрядного десятичного ввода.
package main
import (
"bufio"
"fmt"
"strconv"
"strings"
)
func main() {
// An artificial input source.
const input = "1234 5678 1234567901234567890"
scanner := bufio.NewScanner(strings.NewReader(input))
// Create a custom split function by wrapping the existing ScanWords function.
split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
advance, token, err = bufio.ScanWords(data, atEOF)
if err == nil && token != nil {
_, err = strconv.ParseInt(string(token), 10, 32)
}
return
}
// Set the split function for the scanning operation.
scanner.Split(split)
// Validate the input
for scanner.Scan() {
fmt.Printf("%s\n", scanner.Text())
}
if err := scanner.Err(); err != nil {
fmt.Printf("Invalid input: %s", err)
}
}
Output:
1234
5678
Invalid input: strconv.ParseInt: parsing "1234567901234567890": value out of rangeПример EarlyStop
Используйте сканер с настраиваемой функцией разделения для разбора списка, разделенного запятыми, с пустым конечным значением, но останавливающимся на токене «STOP».
package main
import (
"bufio"
"bytes"
"fmt"
"os"
"strings"
)
func main() {
onComma := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
i := bytes.IndexByte(data, ',')
if i == -1 {
if !atEOF {
return 0, nil, nil
}
// If we have reached the end, return the last token.
return 0, data, bufio.ErrFinalToken
}
// If the token is "STOP", stop the scanning and ignore the rest.
if string(data[:i]) == "STOP" {
return i + 1, nil, bufio.ErrFinalToken
}
// Otherwise, return the token before the comma.
return i + 1, data[:i], nil
}
const input = "1,2,STOP,4,"
scanner := bufio.NewScanner(strings.NewReader(input))
scanner.Split(onComma)
for scanner.Scan() {
fmt.Printf("Got a token %q\n", scanner.Text())
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading input:", err)
}
}
Output:
Got a token "1"
Got a token "2"Пример EmptyFinalToken
Используйте сканер с настраиваемой функцией разделения для разбора списка, разделенного запятыми, с пустым конечным значением.
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
// Comma-separated list; last entry is empty.
const input = "1,2,3,4,"
scanner := bufio.NewScanner(strings.NewReader(input))
// Define a split function that separates on commas.
onComma := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
for i := 0; i < len(data); i++ {
if data[i] == ',' {
return i + 1, data[:i], nil
}
}
if !atEOF {
return 0, nil, nil
}
// There is one final token to be delivered, which may be the empty string.
// Returning bufio.ErrFinalToken here tells Scan there are no more tokens after this
// but does not trigger an error to be returned from Scan itself.
return 0, data, bufio.ErrFinalToken
}
scanner.Split(onComma)
// Scan.
for scanner.Scan() {
fmt.Printf("%q ", scanner.Text())
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading input:", err)
}
}
Output:
"1" "2" "3" "4" ""Пример Lines
Самый простой способ использования сканера — чтение стандартного ввода в виде набора строк.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
fmt.Println(scanner.Text()) // Println will add back the final '\n'
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading standard input:", err)
}
}
Пример Words
Используйте сканер для реализации простой утилиты подсчета слов, сканируя входные данные как последовательность токенов, разделенных пробелами.
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
// An artificial input source.
const input = "Now is the winter of our discontent,\nMade glorious summer by this sun of York.\n"
scanner := bufio.NewScanner(strings.NewReader(input))
// Set the split function for the scanning operation.
scanner.Split(bufio.ScanWords)
// Count the words.
count := 0
for scanner.Scan() {
count++
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading input:", err)
}
fmt.Printf("%d\n", count)
}
Output:
15func NewScanner
func NewScanner(r io.Reader) *Scanner
NewScanner возвращает новый сканер для чтения из r. По умолчанию функция разделения — ScanLines.
func (*Scanner) Buffer
func (s *Scanner) Buffer(buf []byte, max int)
Buffer устанавливает начальный буфер, который будет использоваться при сканировании, и максимальный размер буфера, который может быть выделен во время сканирования. Максимальный размер токена должен быть меньше большего из max и cap(buf). Если max <= cap(buf), Scanner.Scan будет использовать только этот буфер и не будет выделять дополнительную память.
По умолчанию Scanner.Scan использует внутренний буфер и устанавливает максимальный размер токена равным MaxScanTokenSize.
Buffer вызывает панику, если его вызвать после начала сканирования.
func (*Scanner) Bytes
func (s *Scanner) Bytes() []byte
Bytes возвращает самый последний токен, сгенерированный вызовом Scanner.Scan. Базовый массив может указывать на данные, которые будут перезаписаны последующим вызовом Scan. Он не выполняет выделение памяти.
Пример
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
scanner := bufio.NewScanner(strings.NewReader("gopher"))
for scanner.Scan() {
fmt.Println(len(scanner.Bytes()) == 6)
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "shouldn't see an error scanning a string")
}
}
Output:
truefunc (*Scanner) Err
func (s *Scanner) Err() error
Err возвращает первую ошибку, не являющуюся EOF, которая была обнаружена Scanner.
func (*Scanner) Scan
func (s *Scanner) Scan() bool
Scan перемещает Scanner к следующему токену, который затем будет доступен через метод Scanner.Bytes или Scanner.Text. Он возвращает false, когда больше нет токенов, либо по достижении конца ввода, либо из-за ошибки. После того, как Scan возвращает false, метод Scanner.Err вернет любую ошибку, которая произошла во время сканирования, за исключением того, что если это было io.EOF, Scanner.Err вернет nil. Scan вызывает панику, если функция split возвращает слишком много пустых токенов, не продвигая ввод. Это обычный режим ошибки для сканеров.
func (*Scanner) Split
func (s *Scanner) Split(split SplitFunc)
Split устанавливает функцию разделения для сканера. Функция разделения по умолчанию — ScanLines.
Split вызывает панику, если его вызывают после начала сканирования.
func (*Scanner) Text
добавлено в go1.1
func (s *Scanner) Text() string
Text возвращает последний токен, сгенерированный вызовом Scanner.Scan, в виде вновь выделенной строки, содержащей его байты.
type SplitFunc
type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)
SplitFunc — это сигнатура функции разделения, используемой для токенизации входных данных. Аргументами являются начальная подстрока оставшихся необработанных данных и флаг atEOF, который сообщает, есть ли у Reader еще данные для передачи. Возвращаемые значения — это количество байтов для продвижения входных данных и следующий токен для возврата пользователю, если таковой имеется, плюс ошибка, если таковая имеется.
Сканирование останавливается, если функция возвращает ошибку, и в этом случае часть ввода может быть отброшена. Если эта ошибка — ErrFinalToken, сканирование останавливается без ошибки. Токен, отличный от nil, доставленный с ErrFinalToken, будет последним токеном, а токен nil с ErrFinalToken немедленно останавливает сканирование.
В противном случае сканер продвигает ввод. Если токен не равен nil, сканер возвращает его пользователю. Если токен равен nil, сканер считывает больше данных и продолжает сканирование; если данных больше нет (если atEOF было true), сканер возвращается. Если данные еще не содержат полный токен, например, если при сканировании строк в них нет символа новой строки, SplitFunc может вернуть (0, nil, nil), чтобы сигнализировать сканеру о необходимости считывать больше данных в срез и повторить попытку с более длинным срезом, начиная с той же точки во входных данных.
Функция никогда не вызывается с пустым фрагментом данных, если atEOF не равно true. Однако, если atEOF равно true, данные могут быть непустыми и, как всегда, содержать необработанный текст.
Объяснение SplitFunc
Тип SplitFunc в пакете bufio
SplitFunc - это специальный тип функции, который определяет как сканер (Scanner) должен разделять входные данные на токены. Это ключевой механизм, обеспечивающий гибкость работы с текстовыми данными.
Для чего нужен SplitFunc?
Определение логики разделения данных:
- Где заканчивается один токен и начинается следующий
- Как обрабатывать неполные данные
- Как реагировать на ошибки
Поддержка различных форматов данных:
- Стандартные (строки, слова, символы)
- Пользовательские (CSV, логи, специфичные форматы)
Обработка сложных случаев:
- Данные, поступающие частями
- Нестандартные разделители
- Составные токены
Как работает SplitFunc
Функция принимает:
data []byte- буфер с данными для анализаatEOF bool- флаг, указывающий, что это конец данных
Возвращает:
advance int- на сколько байт продвинуться в буфереtoken []byte- выделенный токен (может быть nil)err error- ошибка, если возникла
Примеры использования
1. Стандартные функции разделения
// Чтение по строкам (по умолчанию)
scanner.Split(bufio.ScanLines)
// Чтение по словам
scanner.Split(bufio.ScanWords)
// Чтение по символам (рунам)
scanner.Split(bufio.ScanRunes)
// Чтение по байтам
scanner.Split(bufio.ScanBytes)
2. Пользовательская функция разделения
Пример разделения по запятым:
func splitByComma(data []byte, atEOF bool) (advance int, token []byte, err error) {
// Ищем запятую в данных
for i := 0; i < len(data); i++ {
if data[i] == ',' {
// Возвращаем позицию после запятой и токен
return i + 1, data[:i], nil
}
}
// Если достигнут конец данных и есть что возвращать
if atEOF && len(data) > 0 {
return len(data), data, nil
}
// Запрашиваем больше данных
return 0, nil, nil
}
// Использование
scanner.Split(splitByComma)
3. Сложный пример - чтение CSV
func csvSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
if len(data) == 0 {
return 0, nil, nil
}
// Пропускаем кавычки, если есть
inQuotes := false
if data[0] == '"' {
inQuotes = true
data = data[1:]
}
for i := 0; i < len(data); i++ {
switch {
case inQuotes && data[i] == '"' && i+1 < len(data) && data[i+1] == '"':
i++ // пропускаем двойные кавычки
case inQuotes && data[i] == '"':
// Закрывающая кавычка
return i + 2, data[:i], nil
case !inQuotes && data[i] == ',':
// Разделитель полей
return i + 1, data[:i], nil
case !inQuotes && (data[i] == '\r' || data[i] == '\n'):
// Конец строки
return i + 1, data[:i], nil
}
}
if atEOF {
return len(data), data, nil
}
return 0, nil, nil
}
Когда создавать свой SplitFunc?
Нестандартные форматы данных:
- Специфичные логи
- Двоичные протоколы
- Сложные разделители
Оптимизация обработки:
- Когда стандартные функции неэффективны
- Для обработки очень больших файлов
Специальная логика:
- Пропуск комментариев
- Обработка escape-последовательностей
- Валидация данных на лету
Особенности работы
Инкрементная обработка:
- Функция может вызываться много раз для одного токена
- Нужно корректно обрабатывать частичные данные
Управление буфером:
- Можно ограничивать максимальный размер токена
- Важно правильно указывать позицию продвижения
Обработка ошибок:
- Можно возвращать специальные ошибки
bufio.ErrFinalToken- маркер последнего токена
SplitFunc - это мощный механизм, который делает Scanner чрезвычайно гибким инструментом для обработки текстовых данных в Go.
type Writer
type Writer struct {
// contains filtered or unexported fields
}
Writer реализует буферизацию для объекта io.Writer. Если при записи в Writer происходит ошибка, дальнейшая запись данных не будет приниматься, а все последующие операции записи и Writer.Flush будут возвращать ошибку. После записи всех данных клиент должен вызвать метод Writer.Flush, чтобы гарантировать, что все данные были переданы в базовый io.Writer.
Пример
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
w := bufio.NewWriter(os.Stdout)
fmt.Fprint(w, "Hello, ")
fmt.Fprint(w, "world!")
w.Flush() // Don't forget to flush!
}
Output:
Hello, world!func NewWriter
func NewWriter(w io.Writer) *Writer
NewWriter возвращает новый Writer, буфер которого имеет размер по умолчанию. Если аргумент io.Writer уже является Writer с достаточно большим размером буфера, он возвращает базовый Writer.
func NewWriterSize
func NewWriterSize(w io.Writer, size int) *Writer
NewWriterSize возвращает новый Writer, буфер которого имеет как минимум указанный размер. Если аргумент io.Writer уже является Writer с достаточно большим размером, он возвращает базовый Writer.
func (*Writer) Available
func (b *Writer) Available() int
Available возвращает количество неиспользуемых байтов в буфере.
func (*Writer) AvailableBuffer
func (b *Writer) AvailableBuffer() []byte
AvailableBuffer возвращает пустой буфер с емкостью b.Available(). Этот буфер предназначен для добавления и передачи в следующий вызов Writer.Write. Буфер действителен только до следующей операции записи в b.
Пример
package main
import (
"bufio"
"os"
"strconv"
)
func main() {
w := bufio.NewWriter(os.Stdout)
for _, i := range []int64{1, 2, 3, 4} {
b := w.AvailableBuffer()
b = strconv.AppendInt(b, i, 10)
b = append(b, ' ')
w.Write(b)
}
w.Flush()
}
Output:
1 2 3 4func (*Writer) Buffered
func (b *Writer) Buffered() int
Buffered возвращает количество байтов, которые были записаны в текущий буфер.
func (*Writer) Flush
func (b *Writer) Flush() error
Flush записывает все буферизованные данные в базовый io.Writer.
func (*Writer) ReadFrom
func (b *Writer) ReadFrom(r io.Reader) (n int64, err error)
ReadFrom реализует io.ReaderFrom. Если базовый writer поддерживает метод ReadFrom, то вызывается базовый ReadFrom. Если есть буферизованные данные и базовый ReadFrom, то буфер заполняется и записывается перед вызовом ReadFrom.
Пример
package main
import (
"bufio"
"bytes"
"fmt"
"strings"
)
func main() {
var buf bytes.Buffer
writer := bufio.NewWriter(&buf)
data := "Hello, world!\nThis is a ReadFrom example."
reader := strings.NewReader(data)
n, err := writer.ReadFrom(reader)
if err != nil {
fmt.Println("ReadFrom Error:", err)
return
}
if err = writer.Flush(); err != nil {
fmt.Println("Flush Error:", err)
return
}
fmt.Println("Bytes written:", n)
fmt.Println("Buffer contents:", buf.String())
}
Output:
Bytes written: 41
Buffer contents: Hello, world!
This is a ReadFrom example.func (*Writer) Reset
func (b *Writer) Reset(w io.Writer)
Reset удаляет все невыгруженные данные из буфера, очищает все ошибки и сбрасывает b, чтобы записать его вывод в w. Вызов Reset с нулевым значением Writer инициализирует внутренний буфер до размера по умолчанию. Вызов w.Reset(w) (то есть сброс Writer к самому себе) ничего не делает.
func (*Writer) Size
added in go1.10
func (b *Writer) Size() int
Size возвращает размер базового буфера в байтах.
func (*Writer) Write
func (b *Writer) Write(p []byte) (nn int, err error)
Write записывает содержимое p в буфер. Он возвращает количество записанных байтов. Если nn < len(p), он также возвращает ошибку, объясняющую, почему запись не завершена.
func (*Writer) WriteByte
func (b *Writer) WriteByte(c byte) error
WriteByte записывает один байт.
func (*Writer) WriteRune
func (b *Writer) WriteRune(r rune) (size int, err error)
WriteRune записывает одну кодовую точку Unicode, возвращая количество записанных байтов и любую ошибку.
func (*Writer) WriteString
func (b *Writer) WriteString(s string) (int, error)
WriteString записывает строку. Он возвращает количество записанных байтов. Если количество меньше len(s), он также возвращает ошибку с объяснением, почему запись не завершена.
3 - Пакет builtin встроенных функций Go
Элементы, задокументированные здесь, на самом деле не входят в пакет builtin, но их описание здесь позволяет godoc представить документацию для специальных идентификаторов языка.
Константы
const (
true = 0 == 0 // Untyped bool.
false = 0 != 0 // Untyped bool.
)
true и false - это два нетипизированных булевых значения.
const iota = 0 // Untyped int.
iota - это заранее объявленный идентификатор, представляющий нетипизированный целочисленный порядковый номер текущей спецификации const в объявлении const (обычно в круглых скобках). Он имеет нулевую индексацию.
Переменные ¶
var nil Type // Тип должен быть указателем, каналом, func, интерфейсом, map или slice-типом
nil - это заранее объявленный идентификатор, представляющий нулевое значение для указателя, канала, func, интерфейса, map или slice-типа.
3.1 - Функции builtin
func append
func append(slice []Type, elems ...Type) []Type
Встроенная функция append добавляет элементы в конец слайса. Если он имеет достаточную емкость, целевой слайс переразбивается, чтобы вместить новые элементы. Если нет, выделяется новый базовый массив. Append возвращает обновленный слайс. Поэтому необходимо сохранить результат append, часто в переменной, содержащей сам слайс:
slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)
В качестве особого случая допустимо добавлять строку к байтовому слайсу, например:
slice = append([]byte(«hello »), «world»...)
func cap
func cap(v Type) int
Встроенная функция cap возвращает емкость v в зависимости от его типа:
- Массив: количество элементов в v (то же, что len(v)).
- Указатель на массив: количество элементов в *v (то же, что len(v)).
- Срез: максимальная длина, которую может достичь срез при повторном разрезании; если v равно nil, cap(v) равно нулю.
- Канал: емкость буфера канала в единицах элементов; если v равно nil, cap(v) равно нулю.
Для некоторых аргументов, таких как простое массивное выражение, результатом может быть константа. Подробности см. в разделе «Длина и емкость» спецификации языка Go.
func clear
func clear[T ~[]Type | ~map[Type]Type1](t T)
Встроенная функция clear очищает карты и фрагменты.
- Для карт clear удаляет все записи, в результате чего карта становится пустой.
- Для слайсов clear устанавливает все элементы до длины слайса в нулевое значение соответствующего типа элемента.
Если тип аргумента является типовым параметром, набор типов типового параметра должен содержать только типы карт или слайсов, и clear выполняет операцию, подразумеваемую типовым аргументом. Если t равно nil, clear не выполняет никаких действий.
func close
func close(c chan<- Type)
Встроенная функция close закрывает канал, который должен быть либо двунаправленным, либо только для отправки. Она должна выполняться только отправителем, никогда не получателем, и приводит к закрытию канала после получения последнего отправленного значения. После получения последнего значения из закрытого канала c любое получение из c будет выполняться без блокировки, возвращая нулевое значение для элемента канала. Форма
x, ok := <-c
также установит ok в false для закрытого и пустого канала.
func complex
func complex(r, i FloatType) ComplexType
Встроенная функция complex создает комплексное значение из двух значений с плавающей запятой. Действительная и мнимая части должны быть одинакового размера, либо float32, либо float64 (или приравниваемы к ним), и возвращаемое значение будет соответствующим комплексным типом (complex64 для float32, complex128 для float64).
func copy
func copy(dst, src []Type) int
Встроенная функция copy копирует элементы из исходного слайса в целевой слайс. (В качестве особого случая она также копирует байты из строки в слайс байтов.) Исходный и целевой слайсы могут пересекаться. Copy возвращает количество скопированных элементов, которое будет минимальным из len(src) и len(dst).
func delete
func delete(m map[Type]Type1, key Type)
Встроенная функция delete удаляет элемент с указанным ключом (m[key]) из карты. Если m равно nil или такого элемента нет, delete не выполняет никаких действий.
func imag
func imag(c ComplexType) FloatType
Встроенная функция imag возвращает мнимую часть комплексного числа c. Возвращаемое значение будет иметь тип с плавающей запятой, соответствующий типу c.
func len
func len(v Type) int
Встроенная функция len возвращает длину v в соответствии с его типом:
- Массив: количество элементов в v.
- Указатель на массив: количество элементов в *v (даже если v равен nil).
- Срез или карта: количество элементов в v; если v равен nil, len(v) равен нулю.
- Строка: количество байтов в v.
- Канал: количество элементов в очереди (непрочитанных) в буфере канала; если v равно nil, len(v) равно нулю.
Для некоторых аргументов, таких как строковый литерал или простое массивное выражение, результатом может быть константа. Подробности см. в разделе «Длина и емкость» спецификации языка Go.
func make
func make(t Type, size ...IntegerType) Type
Встроенная функция make выделяет и инициализирует объект типа slice, map или chan (только). Как и в случае с new, первый аргумент является типом, а не значением. В отличие от new, тип возвращаемого значения make совпадает с типом его аргумента, а не является указателем на него. Спецификация результата зависит от типа:
- Срез: размер определяет длину. Емкость среза равна его длине. Можно указать второй целочисленный аргумент, чтобы задать другую емкость; она не должна быть меньше длины. Например, make([]int, 0, 10) выделяет базовый массив размером 10 и возвращает срез длиной 0 и емкостью 10, который поддерживается этим базовым массивом.
- Карта: выделяется пустая карта с достаточным пространством для хранения указанного количества элементов. Размер может быть опущен, в этом случае выделяется небольшой начальный размер.
- Канал: буфер канала инициализируется с указанной емкостью буфера. Если размер равен нулю или опущен, канал не имеет буфера.
func max
func max[T cmp.Ordered](x T, y ...T) T
Встроенная функция max возвращает наибольшее значение из фиксированного числа аргументов типов cmp.Ordered. Должно быть как минимум один аргумент. Если T является типом с плавающей запятой и любой из аргументов является NaN, max вернет NaN.
func min
func min[T cmp.Ordered](x T, y ...T) T
Встроенная функция min возвращает наименьшее значение из фиксированного числа аргументов типов cmp.Ordered. Должно быть как минимум один аргумент. Если T является типом с плавающей запятой и любой из аргументов является NaN, min вернет NaN.
func new
func new(Type) *Type
Встроенная функция new выделяет память. Первый аргумент — это тип, а не значение, и возвращаемое значение — это указатель на вновь выделенное нулевое значение этого типа.
func panic
func panic(v any)
Встроенная функция panic останавливает нормальное выполнение текущего goroutine.
Когда функция F вызывает panic, нормальное выполнение F немедленно останавливается. Все функции, выполнение которых было отложено F, выполняются обычным образом, а затем F возвращается к своему вызывающему. Для вызывающего G вызов F ведет себя как вызов panic, прекращая выполнение G и запуская все отложенные функции. Это продолжается до тех пор, пока все функции в выполняющемся goroutine не остановятся в обратном порядке. В этот момент программа завершается с ненулевым кодом выхода. Эта последовательность завершения называется паникой и может контролироваться встроенной функцией recover.
Начиная с Go 1.21, вызов panic с нулевым значением интерфейса или нетипизированным nil вызывает ошибку выполнения (другой вид паники). Настройка GODEBUG panicnil=1 отключает ошибку выполнения.
func print
func print(args ...Type)
Встроенная функция print форматирует свои аргументы способом, специфичным для реализации, и записывает результат в стандартный поток ошибок. Print полезна для начальной загрузки и отладки; ее сохранение в языке не гарантируется.
func println
func println(args ...Type)
Встроенная функция println форматирует свои аргументы способом, специфичным для реализации, и записывает результат в стандартный поток ошибок. Между аргументами всегда добавляются пробелы и добавляется новая строка. Println полезна для начальной настройки и отладки; ее сохранение в языке не гарантируется.
func real
func real(c ComplexType) FloatType
Встроенная функция real возвращает действительную часть комплексного числа c. Возвращаемое значение будет иметь тип с плавающей запятой, соответствующий типу c.
func recover
func recover() any
Встроенная функция recover позволяет программе управлять поведением goroutine, входящего в состояние паники.
Вызов recover внутри отложенной функции (но не в любой функции, вызываемой ею) останавливает последовательность паники, восстанавливая нормальное выполнение и извлекая значение ошибки, переданное вызову panic.
Если recover вызывается вне отложенной функции, он не останавливает последовательность паники. В этом случае, или когда goroutine не входит в состояние паники, recover возвращает nil.
До Go 1.21 recover также возвращала nil, если panic вызывалась с аргументом nil. Подробности см. в разделе [panic].
Пример
Функция recover() используется для перехвата паники (panic) и восстановления нормального выполнения программы. Вот несколько практических примеров:
Пример 1: Базовое использование recover
package main
import "fmt"
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
fmt.Println("Start")
panic("something went wrong")
fmt.Println("This won't be executed")
}
Вывод:
Start
Recovered from panic: something went wrong
Пример 2: Восстановление после деления на ноль
package main
import "fmt"
func safeDivide(a, b int) (result int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered:", r)
result = 0 // устанавливаем значение по умолчанию
}
}()
return a / b // может вызвать панику при b = 0
}
func main() {
fmt.Println(safeDivide(10, 2)) // 5
fmt.Println(safeDivide(10, 0)) // 0 (после восстановления)
fmt.Println("Program continues")
}
Пример 3: Разные типы паники
package main
import "fmt"
func handlePanic() {
if r := recover(); r != nil {
switch v := r.(type) {
case string:
fmt.Println("String panic:", v)
case error:
fmt.Println("Error panic:", v)
default:
fmt.Printf("Unknown panic: %v (%T)\n", v, v)
}
}
}
func main() {
defer handlePanic()
// Можно раскомментировать любой вариант:
panic("string panic")
// panic(fmt.Errorf("error panic"))
// panic(42)
}
Пример 4: recover в горутинах
package main
import (
"fmt"
"time"
)
func worker(id int) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Worker %d recovered: %v\n", id, r)
}
}()
if id == 2 {
panic(fmt.Sprintf("panic in worker %d", id))
}
fmt.Printf("Worker %d working\n", id)
}
func main() {
for i := 1; i <= 3; i++ {
go worker(i)
}
time.Sleep(time.Second) // Даем время горутинам завершиться
}
Пример 5: Вложенные recover
package main
import "fmt"
func inner() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Inner recovered:", r)
}
}()
panic("inner panic")
}
func outer() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Outer recovered:", r)
}
}()
inner()
fmt.Println("This won't be executed")
}
func main() {
outer()
fmt.Println("Program continues")
}
Важные замечания:
recover()работает только внутри defer-функций- Каждый вызов
recover()обрабатывает только одну панику - После восстановления выполнение продолжается после блока defer
- В Go 1.21+
recover()возвращает nil только если паники не было - Не злоупотребляйте recover - используйте только для действительно неожиданных ошибок
3.2 - Типы в пакете builtin
type ComplexType
type ComplexType complex64
ComplexType используется здесь исключительно в целях документирования. Он заменяет любой из типов complex: complex64 или complex128.
type FloatType
type FloatType float32
FloatType используется здесь исключительно в целях документирования. Он заменяет любой из типов float: float32 или float64.
type IntegerType
type IntegerType int
IntegerType используется здесь только в целях документирования. Он заменяет любой тип целого числа: int, uint, int8 и т. д.
type Type
type Type int
Type используется здесь только в целях документирования. Он заменяет любой тип Go, но представляет один и тот же тип для любого вызова функции.
type Type1
type Type1 int
Type1 используется здесь только в целях документирования. Он является заменителем для любого типа Go, но представляет один и тот же тип для любого вызова функции.
type any
type any = interface{}
any является псевдонимом для interface{} и во всех отношениях эквивалентен interface{}.
type bool
type bool bool
bool — это набор булевых значений true и false.
type byte
type byte = uint8
byte — это псевдоним для uint8 и во всех отношениях эквивалентен uint8. По соглашению он используется для отличия значений byte от 8-битных целых чисел без знака.
type comparable
type comparable interface{ comparable }
comparable — это интерфейс, который реализуется всеми сопоставимыми типами (булевыми значениями, числами, строками, указателями, каналами, массивами сопоставимых типов, структурами, все поля которых являются сопоставимыми типами). Интерфейс comparable может использоваться только в качестве ограничения параметра типа, а не в качестве типа переменной.
Подробнее
Интерфейс comparable - это специальный встроенный интерфейс в Go, который представляет все типы, поддерживающие операции сравнения == и !=. Он используется исключительно как ограничение (constraint) для параметров типа в дженериках.
Особенности comparable:
- Неявная реализация - все сопоставимые типы автоматически удовлетворяют этому интерфейсу
- Ограниченное использование - может применяться только как ограничение типа в дженериках
- Строгая типизация - гарантирует, что типы можно сравнивать между собой
Пример 1: Простая функция сравнения
package main
import "fmt"
// Equal проверяет равенство двух значений comparable типа
func Equal[T comparable](a, b T) bool {
return a == b
}
func main() {
fmt.Println(Equal(1, 1)) // true
fmt.Println(Equal("a", "b")) // false
fmt.Println(Equal(true, false)) // false
}
Пример 2: Проверка наличия элемента в слайсе
func Contains[T comparable](slice []T, item T) bool {
for _, v := range slice {
if v == item {
return true
}
}
return false
}
func main() {
nums := []int{1, 2, 3}
fmt.Println(Contains(nums, 2)) // true
fmt.Println(Contains(nums, 5)) // false
strs := []string{"a", "b", "c"}
fmt.Println(Contains(strs, "b")) // true
}
Пример 3: Уникальные элементы в слайсе
func Unique[T comparable](slice []T) []T {
seen := make(map[T]bool)
result := []T{}
for _, v := range slice {
if !seen[v] {
seen[v] = true
result = append(result, v)
}
}
return result
}
func main() {
dupes := []int{1, 2, 2, 3, 4, 4, 5}
fmt.Println(Unique(dupes)) // [1 2 3 4 5]
}
Какие типы являются comparable:
- Базовые типы:
bool,int,float64,stringи т.д. - Указатели (
*T) - Каналы (
chan T) - Массивы, если их элементы comparable (
[3]int,[2]string) - Структуры, если все их поля comparable
Какие типы НЕ являются comparable:
- Слайсы (
[]T) - Мапы (
map[K]V) - Функции
- Структуры, содержащие несопоставимые поля
Пример 4: Ошибка при использовании несопоставимого типа
func main() {
// Ошибка: slice does not satisfy comparable
fmt.Println(Equal([]int{1}, []int{1}))
// Ошибка: map does not satisfy comparable
fmt.Println(Equal(map[int]int{}, map[int]int{}))
}
Практическое применение:
- Создание универсальных контейнеров
- Реализация алгоритмов для разных типов
- Написание тестовых утилит
- Создание библиотечных функций
type complex128
type complex128 complex128
complex128 — это набор всех комплексных чисел с вещественными и мнимыми частями float64.
type complex64
type complex64 complex64
complex64 — это набор всех комплексных чисел с вещественными и мнимыми частями типа float32.
type error
type error интерфейс {
Error() string
}
Встроенный интерфейсный тип error — это стандартный интерфейс для представления условия ошибки, причем значение nil означает отсутствие ошибки.
type float32
type float32 float32
float32 — это набор всех 32-разрядных чисел с плавающей запятой IEEE 754.
type float64
type float64 float64
float64 — это набор всех 64-разрядных чисел с плавающей запятой IEEE 754.
type int
type int int
int — это тип целых чисел с знаком, размер которого составляет не менее 32 бит. Однако это отдельный тип, а не псевдоним, например, int32.
type int16
type int16 int16
int16 — это набор всех 16-разрядных целых чисел со знаком. Диапазон: от -32768 до 32767.
type int32
type int32 int32
int32 — это набор всех 32-разрядных целых чисел со знаком. Диапазон: от -2147483648 до 2147483647.
type int64
type int64 int64
int64 — это набор всех 64-разрядных целых чисел со знаком. Диапазон: от -9223372036854775808 до 9223372036854775807.
type int8
type int8 int8
int8 — это набор всех 8-битных целых чисел со знаком. Диапазон: от -128 до 127.
type rune
type rune = int32
rune — это псевдоним для int32 и во всех отношениях эквивалентен int32. По соглашению он используется для отличия значений символов от целочисленных значений.
type string
type string string
string — это набор всех строк из 8-битных байтов, которые по соглашению, но не обязательно, представляют текст, закодированный в UTF-8. Строка может быть пустой, но не может быть nil. Значения типа string являются неизменяемыми.
type uint
type uint uint
uint — это тип целого числа без знака, размер которого составляет не менее 32 бит. Однако это отдельный тип, а не псевдоним, например, для uint32.
type uint16
type uint16 uint16
uint16 — это набор всех 16-битных целых чисел без знака. Диапазон: от 0 до 65535.
type uint32
type uint32 uint32
uint32 — это набор всех 32-битных целых чисел без знака. Диапазон: от 0 до 4294967295.
type uint64
type uint64 uint64
uint64 — это набор всех 64-разрядных целых чисел без знака. Диапазон: от 0 до 18446744073709551615.
type uint8
type uint8 uint8
uint8 — это набор всех 8-разрядных целых чисел без знака. Диапазон: от 0 до 255.
type uintptr
type uintptr uintptr
uintptr — это целочисленный тип, который достаточно велик, чтобы содержать битовую матрицу любого указателя.
4 - Описание пакета context языка программирования Go
Входящие запросы к серверу должны создавать Context, а исходящие вызовы к серверам должны принимать Context. Цепочка вызовов функций между ними должна передавать Context, при необходимости заменяя его производным Context, созданным с помощью WithCancel, WithDeadline, WithTimeout или WithValue.
Контекст может быть отменен, чтобы указать, что работа, выполняемая от его имени, должна быть остановлена. Контекст с крайним сроком отменяется после истечения крайнего срока. Когда контекст отменяется, все производные от него контексты также отменяются.
Функции WithCancel, WithDeadline и WithTimeout принимают контекст (родительский) и возвращают производный контекст (дочерний) и CancelFunc. Прямой вызов CancelFunc отменяет дочерний контекст и его дочерние контексты, удаляет ссылку родительского контекста на дочерний и останавливает все связанные таймеры. Невыполнение вызова CancelFunc приводит к утечке дочернего контекста и его дочерних контекстов до тех пор, пока не будет отменен родительский контекст. Инструмент go vet проверяет, что CancelFuncs используются на всех путях управления потоком.
Функции WithCancelCause, WithDeadlineCause и WithTimeoutCause возвращают CancelCauseFunc, который принимает ошибку и записывает ее как причину отмены. Вызов Cause на отмененном контексте или любом из его дочерних элементов извлекает причину. Если причина не указана, Cause(ctx) возвращает то же значение, что и ctx.Err().
Программы, использующие контексты, должны следовать этим правилам, чтобы обеспечить согласованность интерфейсов между пакетами и позволить инструментам статического анализа проверять распространение контекста:
Не храните контексты внутри типа struct; вместо этого явно передавайте контекст каждой функции, которой он нужен. Это подробнее обсуждается в разделе «https://go.dev/blog/context-and-structs». Контекст должен быть первым параметром, обычно называемым ctx:
func DoSomething(ctx context.Context, arg Arg) error {
// ... использовать ctx ...
}
Не передавайте nil Context, даже если функция это допускает. Передайте context.TODO, если не уверены, какой Context использовать.
Используйте значения контекста только для данных в рамках запроса, которые проходят через процессы и API, а не для передачи опциональных параметров функциям.
Один и тот же Context может передаваться функциям, выполняющимся в разных goroutines; Context безопасен для одновременного использования несколькими goroutines.
См. https://go.dev/blog/context для примера кода сервера, использующего Context.
Переменные пакета
var Canceled = errors.New(«context canceled»)
Canceled — это ошибка, возвращаемая [Context.Err], когда контекст отменяется по какой-либо причине, кроме истечения срока.
var DeadlineExceeded error = deadlineExceededError{}
DeadlineExceeded — это ошибка, возвращаемая [Context.Err], когда контекст отменяется из-за истечения срока.
4.1 - Описание функций пакета Context
func AfterFunc
func AfterFunc(ctx Context, f func()) (stop func() bool)
AfterFunc организует вызов f в собственной горутине после отмены ctx. Если ctx уже отменен, AfterFunc вызывает f сразу в своей собственной горутине.
Несколько вызовов AfterFunc на одном контексте работают независимо; один не заменяет другой.
Вызов возвращаемой функции stop прекращает ассоциацию ctx с f. Он возвращает true, если вызов остановил запуск f. Если stop возвращает false, то либо контекст отменен и f была запущена в своей собственной goroutine, либо f уже была остановлена. Функция stop не ждет завершения f перед возвратом. Если вызывающей стороне необходимо знать, завершено ли выполнение f, она должна явно согласовать это с f.
Если у ctx есть метод “AfterFunc(func()) func() bool”, AfterFunc будет использовать его для планирования вызова.
Пример (Cond)
В этом примере AfterFunc используется для определения функции, которая ожидает синхронизацию с Cond, прекращая ожидание при отмене контекста.
package main
import (
"context"
"fmt"
"sync"
"time"
)
func main() {
waitOnCond := func(ctx context.Context, cond *sync.Cond, conditionMet func() bool) error {
// Создаем канал для отмены AfterFunc
done := make(chan struct{})
defer close(done)
// Настраиваем функцию отмены по таймауту контекста
stopf := context.AfterFunc(ctx, func() {
cond.L.Lock()
defer cond.L.Unlock()
select {
case <-done:
// Уже завершились, не нужно broadcast
return
default:
// Пробуждаем все ожидающие горутины
cond.Broadcast()
}
})
defer stopf()
// Ожидаем выполнения условия
for !conditionMet() {
// Проверяем контекст перед ожиданием
if ctx.Err() != nil {
return ctx.Err()
}
cond.Wait()
// Проверяем контекст после пробуждения
if ctx.Err() != nil {
return ctx.Err()
}
}
return nil
}
cond := sync.NewCond(&sync.Mutex{})
var wg sync.WaitGroup
start := time.Now()
for i := 0; i < 4; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
cond.L.Lock()
defer cond.L.Unlock()
err := waitOnCond(ctx, cond, func() bool { return false })
fmt.Printf("Goroutine %d finished after %v with error: %v\n",
id, time.Since(start), err)
}(i)
}
wg.Wait()
fmt.Println("All goroutines completed")
}
Пример Connection
В этом примере используется AfterFunc для определения функции, которая считывает данные из net.Conn, останавливая считывание при отмене контекста.
package main
import (
"context"
"fmt"
"net"
"time"
)
func readFromConn(ctx context.Context, conn net.Conn, b []byte) (n int, err error) {
stopc := make(chan struct{})
defer close(stopc) // Гарантируем закрытие канала
// Устанавливаем функцию отмены чтения при отмене контекста
stop := context.AfterFunc(ctx, func() {
conn.SetReadDeadline(time.Now()) // Прерываем текущее чтение
close(stopc)
})
defer func() {
if !stop() {
// Если AfterFunc был запущен, сбрасываем дедлайн
<-stopc
conn.SetReadDeadline(time.Time{})
}
}()
n, err = conn.Read(b)
if ctx.Err() != nil {
return 0, ctx.Err() // Возвращаем ошибку контекста если он отменен
}
return n, err
}
func main() {
// Создаем тестовый сервер
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
fmt.Println("Failed to create listener:", err)
return
}
defer listener.Close()
// Устанавливаем соединение
conn, err := net.Dial(listener.Addr().Network(), listener.Addr().String())
if err != nil {
fmt.Println("Failed to dial:", err)
return
}
defer conn.Close()
// Создаем контекст с таймаутом
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
// Читаем данные
b := make([]byte, 1024)
_, err = readFromConn(ctx, conn, b)
// Проверяем тип ошибки
switch {
case err == nil:
fmt.Println("Read completed successfully")
case ctx.Err() != nil:
fmt.Println("Operation canceled:", ctx.Err())
default:
fmt.Println("Read error:", err)
}
}
Дополнительные рекомендации:
Таймауты соединения:
conn.SetDeadline(time.Now().Add(30 * time.Second))Буферизация:
bufReader := bufio.NewReader(conn) n, err = bufReader.Read(b)Повторные попытки:
for retries := 0; retries < 3; retries++ { n, err = readFromConn(ctx, conn, b) if err == nil || !isTemporary(err) { break } }Логирование:
log.Printf("Read %d bytes, error: %v", n, err)
Пример Merge
В этом примере AfterFunc используется для определения функции, которая объединяет сигналы отмены двух Контекстов.
package main
import (
"context"
"errors"
"fmt"
"sync"
)
func main() {
// mergeCancel возвращает контекст, который отменяется при отмене любого из исходных контекстов
mergeCancel := func(ctx1, ctx2 context.Context) (context.Context, context.CancelFunc) {
mergedCtx, cancel := context.WithCancelCause(ctx1)
var once sync.Once
// Отслеживаем отмену первого контекста
go func() {
select {
case <-ctx1.Done():
once.Do(func() {
cancel(context.Cause(ctx1))
})
case <-ctx2.Done():
once.Do(func() {
cancel(context.Cause(ctx2))
})
case <-mergedCtx.Done():
// Уже отменен другим путем
}
}()
return mergedCtx, func() {
once.Do(func() {
cancel(context.Canceled)
})
}
}
// Создаем два контекста с возможностью отмены
ctx1, cancel1 := context.WithCancelCause(context.Background())
defer cancel1(errors.New("ctx1 canceled"))
ctx2, cancel2 := context.WithCancelCause(context.Background())
// Объединяем контексты
mergedCtx, mergedCancel := mergeCancel(ctx1, ctx2)
defer mergedCancel()
// Отменяем второй контекст
cancel2(errors.New("ctx2 canceled"))
// Ждем отмены объединенного контекста
<-mergedCtx.Done()
// Выводим причину отмены
fmt.Println("Merged context canceled because:", context.Cause(mergedCtx))
}
Ключевые моменты:
Потокобезопасность:
var once sync.Once once.Do(func() { cancel(context.Cause(ctx)) })Полное отслеживание контекстов:
select { case <-ctx1.Done(): case <-ctx2.Done(): case <-mergedCtx.Done(): }Четкая причина отмены:
fmt.Println("Merged context canceled because:", context.Cause(mergedCtx))Гарантированная очистка:
defer mergedCancel()
func Cause
func Cause(c Context) error
Cause возвращает не нулевую ошибку, объясняющую, почему c был отменен. Первая отмена c или одного из его родителей устанавливает причину. Если эта отмена произошла через вызов CancelCauseFunc(err), то Cause возвращает err. В противном случае Cause(c) возвращает то же значение, что и c.Err(). Cause возвращает nil, если c еще не был отменен.
func WithCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
WithCancel возвращает производный контекст, который указывает на родительский контекст, но имеет новый канал Done. Канал Done возвращенного контекста закрывается при вызове возвращенной функции отмены или при закрытии канала Done родительского контекста, в зависимости от того, что произойдет раньше.
Отмена этого контекста освобождает связанные с ним ресурсы, поэтому код должен вызывать отмену, как только операции, выполняемые в этом контексте, завершатся.
Пример
Этот пример демонстрирует использование отменяемого контекста для предотвращения утечки данных из горутины. К концу выполнения функции примера горутина, запущенная gen, вернется без утечки.
package main
import (
"context"
"fmt"
"sync"
)
func main() {
// gen генерирует целые числа в отдельной горутине и
// отправляет их в возвращаемый канал.
// Вызывающая сторона должна отменить контекст после
// завершения потребления чисел, чтобы избежать утечки горутины.
gen := func(ctx context.Context) <-chan int {
dst := make(chan int)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
defer close(dst) // Закрываем канал при завершении
n := 1
for {
select {
case <-ctx.Done():
return
case dst <- n:
n++
}
}
}()
// Горутина для безопасного завершения
go func() {
wg.Wait()
}()
return dst
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // Отменяем контекст при завершении
for n := range gen(ctx) {
fmt.Println(n)
if n == 5 {
break
}
}
// Дополнительная отмена контекста (хотя defer уже сделает это)
cancel()
}
func WithCancelCause
func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc)
WithCancelCause ведет себя как WithCancel, но возвращает CancelCauseFunc вместо CancelFunc. Вызов cancel с no-nil ошибкой (причина) записывает эту ошибку в ctx; затем ее можно получить с помощью Cause(ctx). Вызов cancel с nil устанавливает причину в Canceled.
Пример использования:
ctx, cancel := context.WithCancelCause(parent)
cancel(myError)
ctx.Err() // возвращает context.Canceled
context.Cause(ctx) // возвращает myError
func WithDeadline
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
WithDeadline возвращает производный контекст, который указывает на родительский контекст, но имеет срок, скорректированный так, чтобы он не был позднее d. Если срок родительского контекста уже раньше d, WithDeadline(parent, d) семантически эквивалентен parent. Возвращаемый канал [Context.Done] закрывается по истечении срока, при вызове возвращаемой функции cancel или при закрытии канала Done родительского контекста, в зависимости от того, что произойдет раньше.
Отмена этого контекста освобождает связанные с ним ресурсы, поэтому код должен вызывать отмену, как только операции, выполняемые в этом контексте, завершатся.
Пример
В этом примере контекст с произвольным сроком передается блокирующей функции, чтобы сообщить ей, что она должна прекратить свою работу, как только до нее доберется.
package main
import (
"context"
"fmt"
"time"
)
func main() {
shortDuration := 50 * time.Millisecond // Явное указание времени таймаута
d := time.Now().Add(shortDuration)
// Создаем контекст с дедлайном
ctx, cancel := context.WithDeadline(context.Background(), d)
defer cancel() // Важно вызывать cancel для освобождения ресурсов
neverReady := make(chan struct{}) // Канал, который никогда не будет готов
select {
case <-neverReady:
fmt.Println("ready") // Эта ветка никогда не выполнится
case <-ctx.Done():
// Проверяем причину завершения контекста
switch ctx.Err() {
case context.DeadlineExceeded:
fmt.Println("context deadline exceeded")
case context.Canceled:
fmt.Println("context canceled")
default:
fmt.Println("context done for unknown reason")
}
}
}
func WithDeadlineCause
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc)
WithDeadlineCause ведет себя как WithDeadline, но также устанавливает причину возвращаемого контекста при превышении срока. Возвращаемая CancelFunc не устанавливает причину.
func WithTimeout
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
WithTimeout возвращает WithDeadline(parent, time.Now().Add(timeout)).
Отмена этого контекста освобождает связанные с ним ресурсы, поэтому код должен вызывать отмену, как только операции, выполняемые в этом контексте, завершатся:
func slowOperationWithTimeout(ctx context.Context) (Result, error) {
ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
defer cancel() // освобождает ресурсы, если slowOperation завершается до истечения времени ожидания
return slowOperation(ctx)
}
Пример
В этом примере контекст передается с таймаутом, чтобы сообщить блокирующей функции, что она должна завершить свою работу по истечении таймаута.
package main
import (
"context"
"fmt"
"time"
)
func main() {
shortDuration := 100 * time.Millisecond // Явное указание времени таймаута
// Создаем контекст с таймаутом
ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
defer cancel() // Всегда вызываем cancel для освобождения ресурсов
neverReady := make(chan struct{}) // Создаем канал, который никогда не закроется
select {
case <-neverReady:
fmt.Println("ready") // Эта ветка никогда не выполнится
case <-ctx.Done():
// Проверяем причину завершения контекста
switch err := ctx.Err(); err {
case context.DeadlineExceeded:
fmt.Println("context deadline exceeded")
case context.Canceled:
fmt.Println("context canceled")
default:
fmt.Printf("context done: %v\n", err)
}
}
}
func WithTimeoutCause
func WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, CancelFunc)
WithTimeoutCause ведет себя как WithTimeout, но также устанавливает причину возвращаемого Context при истечении таймаута. Возвращаемый CancelFunc не устанавливает причину.
4.2 - Описание типов пакета Context
type CancelCauseFunc
type CancelCauseFunc func(cause error)
CancelCauseFunc ведет себя как CancelFunc, но дополнительно устанавливает причину отмены. Эту причину можно получить, вызвав Cause на отмененном Context или на любом из его производных Context.
Если контекст уже был отменен, CancelCauseFunc не устанавливает причину. Например, если childContext является производным от parentContext:
- если parentContext отменен с причиной cause1 до того, как childContext отменен с причиной cause2, то Cause(parentContext) == Cause(childContext) == cause1
- если childContext отменен с причиной cause2 до того, как parentContext отменен с причиной cause1, то Cause(parentContext) == cause1 и Cause(childContext) == cause2
type CancelFunc
type CancelFunc func()
CancelFunc сообщает операции о необходимости прекратить работу. CancelFunc не ждет, пока работа будет остановлена. CancelFunc может вызываться несколькими goroutines одновременно. После первого вызова последующие вызовы CancelFunc ничего не делают.
type Context
type Context interface {
// Deadline возвращает время, когда работа, выполняемая от имени этого
// контекста должна быть отменена. Deadline возвращает ok==false, если
// срок не установлен. Последовательные вызовы Deadline возвращают
// одинаковые результаты.
Deadline() (deadline time.Time, ok bool)
// Done возвращает канал, который закрывается, когда работа, выполняемая
// от имени этого контекста, должна быть отменена. Done может возвращать
// nil, если этот контекст никогда не может быть отменен. Последовательные
// вызовы Done возвращают одинаковое значение.
// Закрытие канала Done может происходить асинхронно,
// после возврата функции cancel.
//
// WithCancel организует закрытие Done при вызове cancel;
// WithDeadline организует закрытие Done по истечении срока;
// WithTimeout организует закрытие Done по истечении таймаута
//.
//
// Done предоставляется для использования в операторах select:
//
// // Stream генерирует значения с помощью DoSomething и отправляет их в out,
// // пока DoSomething не вернет ошибку или ctx.Done не будет закрыт.
// func Stream(ctx context.Context, out chan<- Value) error {
// for {
// v, err := DoSomething(ctx)
// if err != nil {
// return err
// }
// select {
// case <-ctx.Done():
// return ctx.Err()
// case out <- v:
// }
// }
// }
//
// См. https://blog.golang.org/pipelines для получения дополнительных примеров
// использования канала Done для отмены.
Done() <-chan struct{}
// Если Done еще не закрыт, Err возвращает nil.
// Если Done закрыт, Err возвращает no-nil ошибку, объясняющую причину:
// DeadlineExceeded, если срок контекста истек, или Canceled, если контекст
// был отменен по какой-либо другой причине.
// После того, как Err возвращает ошибку, отличную от nil, последующие вызовы
// Err возвращают ту же ошибку.
Err() error
// Value возвращает значение, связанное с этим контекстом для ключа, или nil,
// если с ключом не связано никакое значение. Последовательные вызовы Value с
// одним и тем же ключом возвращают один и тот же результат.
//
// Используйте значения контекста только для данных в рамках запроса, которые
// проходят через процессы и границы API, а не для передачи опциональных
// параметров в функции.
//
// Ключ идентифицирует конкретное значение в контексте. Функции, которые хотят
// сохранить значения в контексте, обычно выделяют ключ в глобальной
// переменной, а затем используют этот ключ в качестве аргумента для
// context.WithValue и Context.Value. Ключ может быть любого типа, который
// поддерживает равенство;
// пакеты должны определять ключи как неэкспортируемый тип, чтобы избежать коллизий.
//
// Пакеты, которые определяют ключ контекста, должны предоставлять типобезопасные
// аксессоры для значений, хранящихся с использованием этого ключа:
//
// // Пользователь пакета определяет тип User, который хранится в контекстах.
// пакет user
//
// import «context»
//
// // User — это тип значения, хранящегося в Contexts.
// type User struct {...}
//
// // key — это неэкспортируемый тип для ключей, определенных в этом пакете.
// // Это предотвращает конфликты с ключами, определенными в других пакетах.
// type key int
//
// // userKey — ключ для значений user.User в Contexts. Он
// // не экспортируется; клиенты используют user.NewContext и user.FromContext
// // вместо прямого использования этого ключа.
// var userKey key
//
// // NewContext возвращает новый Context, который несет значение u.
// func NewContext(ctx context.Context, u *User) context.Context {
// return context.WithValue(ctx, userKey, u)
// }
//
// // FromContext возвращает значение User, хранящееся в ctx, если оно есть.
// func FromContext(ctx context.Context) (*User, bool) {
// u, ok := ctx.Value(userKey).(*User)
// return u, ok
// }
Value(key any) any
}
Context несет в себе срок, сигнал отмены и другие значения через границы API.
Методы контекста могут вызываться несколькими goroutines одновременно.
func Background
func Background() Context
Background возвращает непустой Context, не равный nil. Он никогда не отменяется, не имеет значений и не имеет срока действия. Обычно он используется главной функцией, инициализацией и тестами, а также в качестве Context верхнего уровня для входящих запросов.
func TODO
func TODO() Context
TODO возвращает непустой Context, не равный nil. Код должен использовать context.TODO, когда неясно, какой Context использовать, или он еще не доступен (потому что окружающая функция еще не была расширена для приема параметра Context).
func WithValue
func WithValue(parent Context, key, val any) Context
WithValue возвращает производный контекст, который указывает на родительский Context. В производном контексте значение, связанное с ключом, является val.
Используйте контекстные значения только для данных в рамках запроса, которые проходят через процессы и API, а не для передачи опциональных параметров функциям.
Предоставленный ключ должен быть сопоставимым и не должен быть типом string или любым другим встроенным типом, чтобы избежать конфликтов между пакетами, использующими контекст. Пользователи WithValue должны определять свои собственные типы для ключей. Чтобы избежать выделения памяти при присвоении interface {}, ключи контекста часто имеют конкретный тип struct {}. В качестве альтернативы статический тип экспортированных переменных ключей контекста должен быть указателем или интерфейсом.
Пример
Этот пример демонстрирует, как можно передать значение в контекст, а также как получить его, если оно существует.
package main
import (
"context"
"fmt"
)
func main() {
// Определяем пользовательский тип для ключей контекста
type favContextKey string
// Функция для поиска значения в контексте
lookupValue := func(ctx context.Context, k favContextKey) {
if v := ctx.Value(k); v != nil {
fmt.Printf("Found value for key '%s': %v\n", k, v)
return
}
fmt.Printf("Key '%s' not found in context\n", k)
}
// Создаем ключ и контекст со значением
languageKey := favContextKey("language")
ctx := context.WithValue(context.Background(), languageKey, "Go")
// Ищем существующее значение
lookupValue(ctx, languageKey)
// Ищем несуществующее значение
colorKey := favContextKey("color")
lookupValue(ctx, colorKey)
// Дополнительная проверка с другим типом (демонстрация безопасности)
otherKey := "otherKey" // обычная строка, не favContextKey
lookupValue(ctx, favContextKey(otherKey)) // конвертируем в правильный тип
}
Дополнительные рекомендации:
- Для production-кода:
// Лучше выносить ключи в package-level константы
const (
LanguageKey favContextKey = "language"
ColorKey favContextKey = "color"
)
- Добавить проверку типа значения:
if v, ok := ctx.Value(k).(string); ok {
fmt.Printf("Found string value: %s\n", v)
}
- Для сложных данных использовать указатели:
type configKey struct{}
ctx = context.WithValue(ctx, configKey{}, &MyConfig{...})
func WithoutCancel
func WithoutCancel(parent Context) Context
WithoutCancel возвращает производный контекст, который указывает на родительский контекст и не отменяется при отмене родительского контекста. Возвращаемый контекст не возвращает Deadline или Err, а его канал Done равен nil. Вызов Cause на возвращаемом контексте возвращает nil.
5 - Пакет bytes языка программирования Go
Константы
const MinRead = 512
MinRead — минимальный размер фрагмента, передаваемый вызову Buffer.Read из Buffer.ReadFrom. Если буфер содержит не менее MinRead байт сверх того, что требуется для хранения содержимого r, Buffer.ReadFrom не будет увеличивать базовый буфер.
Переменные
var ErrTooLarge = errors.New("bytes.Buffer: too large")
ErrTooLarge передается в panic, если не удается выделить память для хранения данных в буфере.
Функции
func Clone
func Clone(b []byte) []byte
Clone возвращает копию b[:len(b)]. Результат может иметь дополнительный неиспользованный объем. Clone(nil) возвращает nil.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
b := []byte("abc")
clone := bytes.Clone(b)
fmt.Printf("%s\n", clone)
clone[0] = 'd'
fmt.Printf("%s\n", b)
fmt.Printf("%s\n", clone)
}
Output:
abc
abc
dbcfunc Compare
func Compare(a, b []byte) int
Compare возвращает целое число, сравнивая два лексикографических фрагмента байтов. Результат будет равен 0, если a == b, -1, если a < b, и +1, если a > b. Аргумент nil эквивалентен пустому фрагменту.
Пример
package main
import (
"bytes"
)
func main() {
// Interpret Compare's result by comparing it to zero.
var a, b []byte
if bytes.Compare(a, b) < 0 {
// a less b
}
if bytes.Compare(a, b) <= 0 {
// a less or equal b
}
if bytes.Compare(a, b) > 0 {
// a greater b
}
if bytes.Compare(a, b) >= 0 {
// a greater or equal b
}
// Prefer Equal to Compare for equality comparisons.
if bytes.Equal(a, b) {
// a equal b
}
if !bytes.Equal(a, b) {
// a not equal b
}
}
Пример Поиск
package main
import (
"bytes"
"slices"
)
func main() {
// Binary search to find a matching byte slice.
var needle []byte
var haystack [][]byte // Assume sorted
_, found := slices.BinarySearchFunc(haystack, needle, bytes.Compare)
if found {
// Found it!
}
}
func Contains
func Contains(b, subslice []byte) bool
Contains сообщает, находится ли подфрагмент в пределах b.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Println(bytes.Contains([]byte("seafood"), []byte("foo")))
fmt.Println(bytes.Contains([]byte("seafood"), []byte("bar")))
fmt.Println(bytes.Contains([]byte("seafood"), []byte("")))
fmt.Println(bytes.Contains([]byte(""), []byte("")))
}
Output:
true
false
true
truefunc ContainsAny
func ContainsAny(b []байт, chars string) bool
ContainsAny сообщает, находится ли какая-либо из кодовых точек в кодировке UTF-8 в chars в пределах b.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Println(bytes.ContainsAny([]byte("I like seafood."), "fÄo!"))
fmt.Println(bytes.ContainsAny([]byte("I like seafood."), "去是伟大的."))
fmt.Println(bytes.ContainsAny([]byte("I like seafood."), ""))
fmt.Println(bytes.ContainsAny([]byte(""), ""))
}
Output:
true
true
false
falsefunc ContainsFunc
func ContainsFunc(b []byte, f func(rune) bool) bool
ContainsFunc сообщает, удовлетворяет ли f(r) любая из кодовых точек r в кодировке UTF-8 в пределах b.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
f := func(r rune) bool {
return r >= 'a' && r <= 'z'
}
fmt.Println(bytes.ContainsFunc([]byte("HELLO"), f))
fmt.Println(bytes.ContainsFunc([]byte("World"), f))
}
Output:
false
truefunc ContainsRune
func ContainsRune(b []byte, r rune) bool
ContainsRune сообщает, содержится ли руна в UTF-8-кодированном байтовом фрагменте b.
func Count
func Count(s, sep []byte) int
Count подсчитывает количество непересекающихся экземпляров sep в s. Если sep - пустой фрагмент, Count возвращает 1 + количество кодовых точек в s, закодированных в UTF-8.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Println(bytes.Count([]byte("cheese"), []byte("e")))
fmt.Println(bytes.Count([]byte("five"), []byte(""))) // before & after each rune
}
Output:
3
5func Cut
func Cut(s, sep []byte) (before, after []byte, found bool)
Cut разрезает s вокруг первого экземпляра sep, возвращая текст до и после sep. Результат found сообщает, появляется ли sep в s. Если sep не появляется в s, cut возвращает s, nil, false.
Cut возвращает фрагменты исходного фрагмента s, а не его копии.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
show := func(s, sep string) {
before, after, found := bytes.Cut([]byte(s), []byte(sep))
fmt.Printf("Cut(%q, %q) = %q, %q, %v\n", s, sep, before, after, found)
}
show("Gopher", "Go")
show("Gopher", "ph")
show("Gopher", "er")
show("Gopher", "Badger")
}
Output:
Cut("Gopher", "Go") = "", "pher", true
Cut("Gopher", "ph") = "Go", "er", true
Cut("Gopher", "er") = "Goph", "", true
Cut("Gopher", "Badger") = "Gopher", "", falsefunc CutPrefix
func CutPrefix(s, prefix []byte) (after []byte, found bool)
CutPrefix возвращает s без предоставленного ведущего префиксного байтового фрагмента и сообщает, найден ли префикс. Если s не начинается с префикса, CutPrefix возвращает s, false. Если префикс - это пустой байтовый фрагмент, CutPrefix возвращает s, true.
CutPrefix возвращает фрагменты исходного фрагмента s, а не его копии.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
show := func(s, sep string) {
after, found := bytes.CutPrefix([]byte(s), []byte(sep))
fmt.Printf("CutPrefix(%q, %q) = %q, %v\n", s, sep, after, found)
}
show("Gopher", "Go")
show("Gopher", "ph")
}
Output:
CutPrefix("Gopher", "Go") = "pher", true
CutPrefix("Gopher", "ph") = "Gopher", falsefunc CutSuffix
func CutSuffix(s, suffix []byte) (before []byte, found bool)
CutSuffix возвращает s без предоставленного завершающего суффикса байтового фрагмента и сообщает, найден ли суффикс. Если s не заканчивается суффиксом, CutSuffix возвращает s, false. Если суффикс - пустой байтовый фрагмент, CutSuffix возвращает s, true.
CutSuffix возвращает фрагменты исходного фрагмента s, а не его копии.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
show := func(s, sep string) {
before, found := bytes.CutSuffix([]byte(s), []byte(sep))
fmt.Printf("CutSuffix(%q, %q) = %q, %v\n", s, sep, before, found)
}
show("Gopher", "Go")
show("Gopher", "er")
}
Output:
CutSuffix("Gopher", "Go") = "Gopher", false
CutSuffix("Gopher", "er") = "Goph", truefunc Equal
func Equal(a, b []byte) bool
Equal сообщает, имеют ли a и b одинаковую длину и содержат ли они одинаковые байты. Аргумент nil эквивалентен пустому фрагменту.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Println(bytes.Equal([]byte("Go"), []byte("Go")))
fmt.Println(bytes.Equal([]byte("Go"), []byte("C++")))
}
Output:
true
falsefunc EqualFold
func EqualFold(s, t []byte) bool
EqualFold сообщает, равны ли s и t, интерпретируемые как строки UTF-8, при простом преобразовании регистра Юникода, которое является более общей формой нечувствительности к регистру.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Println(bytes.EqualFold([]byte("Go"), []byte("go")))
}Output:
truefunc Fields
func Fields(s []byte) [][]byte
Fields интерпретирует s как последовательность кодовых точек, закодированных в UTF-8. Он разделяет срез s вокруг каждого экземпляра одного или нескольких последовательных пробельных символов, как определено в unicode.IsSpace, возвращая срез подсрезов s или пустой срез, если s содержит только пробельные символы.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Printf("Fields are: %q", bytes.Fields([]byte(" foo bar baz ")))
}Output:
Fields are: ["foo" "bar" "baz"]func FieldsFunc
func FieldsFunc(s []byte, f func(rune) bool) [][]byte
FieldsFunc интерпретирует s как последовательность кодовых точек, закодированных в UTF-8. Он разбивает срез s на каждом проходе кодовых точек c, удовлетворяющих f(c), и возвращает срез подсрезов s. Если все кодовые точки в s удовлетворяют f(c) или len(s) == 0, возвращается пустой срез.
FieldsFunc не дает никаких гарантий относительно порядка вызова f(c) и предполагает, что f всегда возвращает одно и то же значение для данного c.
Пример
package main
import (
"bytes"
"fmt"
"unicode"
)
func main() {
f := func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
}
fmt.Printf("Fields are: %q", bytes.FieldsFunc([]byte(" foo1;bar2,baz3..."), f))
}
Output:
Fields are: ["foo1" "bar2" "baz3"]func FieldsFuncSeq
func FieldsFuncSeq(s []byte, f func(rune) bool) iter.Seq[[]byte]
FieldsFuncSeq возвращает итератор над подфрагментами s, разбитыми по последовательностям кодовых точек Unicode, удовлетворяющих f(c). Итератор возвращает те же подфрагменты, которые были бы возвращены FieldsFunc(s), но без создания нового фрагмента, содержащего подфрагменты.
func FieldsSeq
func FieldsSeq(s []byte) iter.Seq[[]byte]
FieldsSeq возвращает итератор по подсрезам s, разделённым пробельными символами (как определено в unicode.IsSpace). Итератор возвращает те же подсрезы, что и функция Fields(s), но без создания нового среза.
func HasPrefix
func HasPrefix(s, prefix []byte) bool
HasPrefix проверяет, начинается ли байтовый срез s с prefix.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Println(bytes.HasPrefix([]byte("Gopher"), []byte("Go")))
fmt.Println(bytes.HasPrefix([]byte("Gopher"), []byte("C")))
fmt.Println(bytes.HasPrefix([]byte("Gopher"), []byte("")))
Output:
true
false
truefunc HasSuffix
func HasSuffix(s, suffix []byte) bool
HasSuffix проверяет, заканчивается ли байтовый срез s на suffix.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Println(bytes.HasSuffix([]byte("Amigo"), []byte("go")))
fmt.Println(bytes.HasSuffix([]byte("Amigo"), []byte("O")))
fmt.Println(bytes.HasSuffix([]byte("Amigo"), []byte("Ami")))
fmt.Println(bytes.HasSuffix([]byte("Amigo"), []byte("")))
}
Output:
true
false
false
truefunc Index
func Index(s, sep []byte) int
Index возвращает индекс первого вхождения sep в s или -1, если sep отсутствует в s.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Println(bytes.Index([]byte("chicken"), []byte("ken")))
fmt.Println(bytes.Index([]byte("chicken"), []byte("dmr")))
}Output:
4
-1func IndexAny
func IndexAny(s []byte, chars string) int
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Println(bytes.IndexAny([]byte("chicken"), "aeiouy"))
fmt.Println(bytes.IndexAny([]byte("crwth"), "aeiouy"))
}
Output:
2
-1IndexAny интерпретирует s как последовательность UTF-8 кодовых точек. Возвращает байтовый индекс первого вхождения любого символа из chars в s. Возвращает -1, если chars пуст или нет совпадений.
func IndexByte
func IndexByte(b []byte, c byte) int
IndexByte возвращает индекс первого вхождения байта c в b или -1, если c отсутствует в b.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Println(bytes.IndexByte([]byte("chicken"), byte('k')))
fmt.Println(bytes.IndexByte([]byte("chicken"), byte('g')))
}Output:
4
-1func IndexFunc
func IndexFunc(s []byte, f func(r rune) bool) int
IndexFunc интерпретирует s как последовательность UTF-8 кодовых точек. Возвращает байтовый индекс первой кодовой точки, удовлетворяющей условию f(c), или -1, если таких нет.
Пример
package main
import (
"bytes"
"fmt"
"unicode"
)
func main() {
f := func(c rune) bool {
return unicode.Is(unicode.Han, c)
}
fmt.Println(bytes.IndexFunc([]byte("Hello, 世界"), f))
fmt.Println(bytes.IndexFunc([]byte("Hello, world"), f))
}
Output:
7
-1func IndexRune
func IndexRune(s []byte, r rune) int
IndexRune интерпретирует s как последовательность UTF-8 кодовых точек. Возвращает байтовый индекс первого вхождения руны r. Если r — utf8.RuneError, возвращает первый индекс невалидной UTF-8 последовательности.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Println(bytes.IndexRune([]byte("chicken"), 'k'))
fmt.Println(bytes.IndexRune([]byte("chicken"), 'd'))
}Output:
4
-1func Join
func Join(s [][]byte, sep []byte) []byte
Join объединяет элементы среза s в новый байтовый срез, разделяя их sep.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
s := [][]byte{[]byte("foo"), []byte("bar"), []byte("baz")}
fmt.Printf("%s", bytes.Join(s, []byte(", ")))
}
Output:
foo, bar, bazfunc LastIndex
func LastIndex(s, sep []byte) int
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Println(bytes.Index([]byte("go gopher"), []byte("go")))
fmt.Println(bytes.LastIndex([]byte("go gopher"), []byte("go")))
fmt.Println(bytes.LastIndex([]byte("go gopher"), []byte("rodent")))
}
Output:
0
3
-1LastIndex возвращает индекс последнего экземпляра sep в s или -1, если sep отсутствует в s.
func LastIndexAny
func LastIndexAny(s []byte, chars string) int
LastIndexAny интерпретирует s как последовательность кодовых точек Unicode в кодировке UTF-8. Возвращает индекс байта последнего вхождения в s любой из кодовых точек Unicode в chars. Возвращает -1, если chars пуст или если нет общей кодовой точки.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Println(bytes.LastIndexAny([]byte("go gopher"), "MüQp"))
fmt.Println(bytes.LastIndexAny([]byte("go 地鼠"), "地大"))
fmt.Println(bytes.LastIndexAny([]byte("go gopher"), "z,!."))
}
Output:
5
3
-1func LastIndexByte
func LastIndexByte(s []byte, c byte) int
LastIndexByte возвращает индекс последнего экземпляра c в s или -1, если c отсутствует в s.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Println(bytes.LastIndexByte([]byte("go gopher"), byte('g')))
fmt.Println(bytes.LastIndexByte([]byte("go gopher"), byte('r')))
fmt.Println(bytes.LastIndexByte([]byte("go gopher"), byte('z')))
}Output:
3
8
-1func LastIndexFunc
func LastIndexFunc(s []byte, f func(r rune) bool) int
LastIndexFunc интерпретирует s как последовательность кодовых точек, закодированных в UTF-8. Он возвращает индекс байта в s последней кодовой точки Unicode, удовлетворяющей f(c), или -1, если таковой нет.
Пример
package main
import (
"bytes"
"fmt"
"unicode"
)
func main() {
fmt.Println(bytes.LastIndexFunc([]byte("go gopher!"), unicode.IsLetter))
fmt.Println(bytes.LastIndexFunc([]byte("go gopher!"), unicode.IsPunct))
fmt.Println(bytes.LastIndexFunc([]byte("go gopher!"), unicode.IsNumber))
}Output:
8
9
-1func Lines
func Lines(s []byte) iter.Seq[[]byte]
Lines возвращает итератор по строкам, завершающимся символом новой строки, в байтовом срезе s. Строки, возвращаемые итератором, включают в себя завершающие символы новой строки. Если s пустой, итератор не возвращает никаких строк. Если s не заканчивается символом новой строки, последняя возвращаемая строка не будет заканчиваться символом новой строки. Возвращает итератор однократного использования.
func Map
func Map(mapping func(r rune) rune, s []byte) []byte
Map возвращает копию байтового среза s со всеми символами, измененными в соответствии с функцией отображения. Если отображение возвращает отрицательное значение, символ удаляется из байтового среза без замены. Символы в s и выводе интерпретируются как кодовые точки, закодированные в UTF-8.
Пример
import (
"bytes"
"fmt"
)
func main() {
rot13 := func(r rune) rune {
switch {
case r >= 'A' && r <= 'Z':
return 'A' + (r-'A'+13)%26
case r >= 'a' && r <= 'z':
return 'a' + (r-'a'+13)%26
}
return r
}
fmt.Printf("%s\n", bytes.Map(rot13, []byte("'Twas brillig and the slithy gopher...")))
}Output:
'Gjnf oevyyvt naq gur fyvgul tbcure...func Repeat
func Repeat(b []byte, count int) []byte
Repeat возвращает новый байтовый срез, состоящий из count копий b.
Он вызывает панику, если count отрицательно или если результат (len(b) * count) переполняется.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Printf("ba%s", bytes.Repeat([]byte("na"), 2))
}
Output:
bananafunc Replace
func Replace(s, old, new []byte, n int) []byte
Replace возвращает копию фрагмента s с первыми n непересекающимися экземплярами old, замененными на new. Если old пуст, то он совпадает в начале фрагмента и после каждой последовательности UTF-8, что позволяет получить до k+1 замен для k-рангового фрагмента. Если n < 0, то количество замен не ограничено.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Printf("%s\n", bytes.Replace([]byte("oink oink oink"), []byte("k"), []byte("ky"), 2))
fmt.Printf("%s\n", bytes.Replace([]byte("oink oink oink"), []byte("oink"), []byte("moo"), -1))
}Output:
oinky oinky oink
moo moo moofunc ReplaceAll
func ReplaceAll(s, old, new []byte) []byte
ReplaceAll возвращает копию фрагмента s со всеми непересекающимися экземплярами old, замененными на new. Если old пуст, то совпадение происходит в начале фрагмента и после каждой последовательности UTF-8, что позволяет получить до k+1 замен для фрагмента длиной в k строк.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Printf("%s\n", bytes.ReplaceAll([]byte("oink oink oink"), []byte("oink"), []byte("moo")))
}
Output:
moo moo moofunc Runes
func Runes(s []byte) []rune
Runes интерпретирует s как последовательность кодовых точек в кодировке UTF-8. Она возвращает фрагмент рун (кодовых точек Юникода), эквивалентный s.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
rs := bytes.Runes([]byte("go gopher"))
for _, r := range rs {
fmt.Printf("%#U\n", r)
}
}
Output:
U+0067 'g'
U+006F 'o'
U+0020 ' '
U+0067 'g'
U+006F 'o'
U+0070 'p'
U+0068 'h'
U+0065 'e'
U+0072 'r'func Split
func Split(s, sep []byte) [][]byte
Split разбивает s на все подфрагменты, разделенные sep, и возвращает фрагмент подфрагментов между этими разделителями. Если sep пуст, Split разделяет после каждой последовательности UTF-8. Это эквивалентно SplitN с числом -1.
Для разбиения вокруг первого экземпляра разделителя смотрите Cut.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Printf("%q\n", bytes.Split([]byte("a,b,c"), []byte(",")))
fmt.Printf("%q\n", bytes.Split([]byte("a man a plan a canal panama"), []byte("a ")))
fmt.Printf("%q\n", bytes.Split([]byte(" xyz "), []byte("")))
fmt.Printf("%q\n", bytes.Split([]byte(""), []byte("Bernardo O'Higgins")))
}
Output:
["a" "b" "c"]
["" "man " "plan " "canal panama"]
[" " "x" "y" "z" " "]
[""]func SplitAfter
func SplitAfter(s, sep []byte) [][]byte
SplitAfter разбивает s на все подфрагменты после каждого экземпляра sep и возвращает фрагмент этих подфрагментов. Если sep пуст, SplitAfter разбивает после каждой последовательности UTF-8. Это эквивалентно SplitAfterN с количеством -1.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Printf("%q\n", bytes.SplitAfter([]byte("a,b,c"), []byte(",")))
}
Output:
["a," "b," "c"]func SplitAfterN
func SplitAfterN(s, sep []byte, n int) [][]byte
SplitAfterN разбивает s на подфрагменты после каждого экземпляра sep и возвращает фрагмент этих подфрагментов. Если sep пуст, SplitAfterN разбивает после каждой последовательности UTF-8. Параметр count определяет количество возвращаемых подфрагментов:
- n > 0: не более n подфрагментов; последний подфрагмент будет нерасщепленным остатком;
- n == 0: результат равен nil (нулевые подмножества);
- n < 0: все поддоли.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Printf("%q\n", bytes.SplitAfterN([]byte("a,b,c"), []byte(","), 2))
}
Output:
["a," "b,c"]func SplitAfterSeq
func SplitAfterSeq(s, sep []byte) iter.Seq[[]byte]
SplitAfterSeq возвращает итератор по подфрагментам s, разделенным после каждого экземпляра sep. Итератор дает те же подфрагменты, которые были бы возвращены SplitAfter(s, sep), но без построения нового фрагмента, содержащего подфрагменты. Возвращается одноразовый итератор.
func SplitN
func SplitN(s, sep []байт, n int) [][]байт
SplitN разбивает s на подфрагменты, разделенные sep, и возвращает фрагмент подфрагментов между этими разделителями. Если sep пуст, SplitN разбивает после каждой последовательности UTF-8. Параметр count определяет количество возвращаемых подфрагментов:
- n > 0: не более n подфрагментов; последний подфрагмент будет нерасщепленным остатком;
- n == 0: результат равен nil (нулевые подмножества);
- n < 0: все подфрагменты.
Для разбиения вокруг первого экземпляра разделителя смотрите Cut.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Printf("%q\n", bytes.SplitN([]byte("a,b,c"), []byte(","), 2))
z := bytes.SplitN([]byte("a,b,c"), []byte(","), 0)
fmt.Printf("%q (nil = %v)\n", z, z == nil)
}Output:
["a" "b,c"]
[] (nil = true)func SplitSeq
func SplitSeq(s, sep []byte) iter.Seq[[]byte]
SplitSeq возвращает итератор по всем подфрагментам s, разделенным sep. Итератор дает те же подфрагменты, которые были бы возвращены функцией Split(s, sep), но без построения нового фрагмента, содержащего эти подфрагменты. Возвращается одноразовый итератор.
func Title (устарело)
func ToLower
func ToLower(s []byte) []byte
ToLower возвращает копию байтового фрагмента s со всеми буквами Юникода, переведенными в нижний регистр.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Printf("%s", bytes.ToLower([]byte("Gopher")))
}
Output:
gopherfunc ToLowerSpecial
func ToLowerSpecial(c unicode.SpecialCase, s []byte) []byte
ToLowerSpecial обрабатывает s как байт в кодировке UTF-8 и возвращает копию со всеми буквами Юникода, переведенными в нижний регистр, отдавая приоритет правилам специального регистра.
Пример
package main
import (
"bytes"
"fmt"
"unicode"
)
func main() {
str := []byte("AHOJ VÝVOJÁRİ GOLANG")
totitle := bytes.ToLowerSpecial(unicode.AzeriCase, str)
fmt.Println("Original : " + string(str))
fmt.Println("ToLower : " + string(totitle))
}Output:
Original : AHOJ VÝVOJÁRİ GOLANG
ToLower : ahoj vývojári golangfunc ToTitle
func ToTitle(s []byte) []byte
ToTitle обрабатывает s как байт в кодировке UTF-8 и возвращает его копию со всеми буквами Юникода, отображенными в регистр заголовка.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Printf("%s\n", bytes.ToTitle([]byte("loud noises")))
fmt.Printf("%s\n", bytes.ToTitle([]byte("брат")))
}
Output:
LOUD NOISES
БРАТfunc ToTitleSpecial
func ToTitleSpecial(c unicode.SpecialCase, s []byte) []byte
ToTitleSpecial обрабатывает s как байты в кодировке UTF-8 и возвращает копию со всеми буквами Юникода, отображенными в их заглавный регистр, отдавая приоритет правилам специального регистра.
Пример
package main
import (
"bytes"
"fmt"
"unicode"
)
func main() {
str := []byte("ahoj vývojári golang")
totitle := bytes.ToTitleSpecial(unicode.AzeriCase, str)
fmt.Println("Original : " + string(str))
fmt.Println("ToTitle : " + string(totitle))
}Output:
Original : ahoj vývojári golang
ToTitle : AHOJ VÝVOJÁRİ GOLANGfunc ToUpper
func ToUpper(s []byte) []byte
ToUpper возвращает копию байтового фрагмента s со всеми буквами Юникода, переведенными в верхний регистр.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Printf("%s", bytes.ToUpper([]byte("Gopher")))
}Output:
GOPHERfunc ToUpperSpecial
func ToUpperSpecial(c unicode.SpecialCase, s []byte) []byte
ToUpperSpecial обрабатывает s как байт в кодировке UTF-8 и возвращает копию со всеми буквами Юникода, переведенными в верхний регистр, отдавая приоритет правилам специального регистра.
Пример
package main
import (
"bytes"
"fmt"
"unicode"
)
func main() {
str := []byte("ahoj vývojári golang")
totitle := bytes.ToUpperSpecial(unicode.AzeriCase, str)
fmt.Println("Original : " + string(str))
fmt.Println("ToUpper : " + string(totitle))
}
Output:
Original : ahoj vývojári golang
ToUpper : AHOJ VÝVOJÁRİ GOLANGfunc ToValidUTF8
func ToValidUTF8(s, replacement []byte) []byte
ToValidUTF8 обрабатывает s как байты в кодировке UTF-8 и возвращает копию, в которой каждый ряд байтов, представляющих недопустимый UTF-8, заменен байтами в replacement, которая может быть пустой.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Printf("%s\n", bytes.ToValidUTF8([]byte("abc"), []byte("\uFFFD")))
fmt.Printf("%s\n", bytes.ToValidUTF8([]byte("a\xffb\xC0\xAFc\xff"), []byte("")))
fmt.Printf("%s\n", bytes.ToValidUTF8([]byte("\xed\xa0\x80"), []byte("abc")))
}Output:
abc
abc
abcfunc Trim
func Trim(s []байт, cutset string) []байт
Trim возвращает подфрагмент s, отсекая все ведущие и последующие кодовые точки в кодировке UTF-8, содержащиеся в cutset.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Printf("[%q]", bytes.Trim([]byte(" !!! Achtung! Achtung! !!! "), "! "))
}Output:
["Achtung! Achtung"]func TrimFunc
func TrimFunc(s []byte, f func(r rune) bool) []byte
TrimFunc возвращает подфрагмент s, отсекая все ведущие и последующие точки кода c в кодировке UTF-8, которые удовлетворяют f(c).
Пример
package main
import (
"bytes"
"fmt"
"unicode"
)
func main() {
fmt.Println(string(bytes.TrimFunc([]byte("go-gopher!"), unicode.IsLetter)))
fmt.Println(string(bytes.TrimFunc([]byte("\"go-gopher!\""), unicode.IsLetter)))
fmt.Println(string(bytes.TrimFunc([]byte("go-gopher!"), unicode.IsPunct)))
fmt.Println(string(bytes.TrimFunc([]byte("1234go-gopher!567"), unicode.IsNumber)))
}
Output:
-gopher!
"go-gopher!"
go-gopher
go-gopher!func TrimLeft
func TrimLeft(s []байт, cutset string) []байт
TrimLeft возвращает подфрагмент s, отсекая все ведущие точки кода в кодировке UTF-8, содержащиеся в cutset.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Print(string(bytes.TrimLeft([]byte("453gopher8257"), "0123456789")))
}
Output:
gopher8257func TrimLeftFunc
func TrimLeftFunc(s []байт, f func(r rune) bool) []байт
TrimLeftFunc рассматривает s как байт в кодировке UTF-8 и возвращает подфрагмент s, отрезая все ведущие кодовые точки c в кодировке UTF-8, которые удовлетворяют f(c).
Пример
package main
import (
"bytes"
"fmt"
"unicode"
)
func main() {
fmt.Println(string(bytes.TrimLeftFunc([]byte("go-gopher"), unicode.IsLetter)))
fmt.Println(string(bytes.TrimLeftFunc([]byte("go-gopher!"), unicode.IsPunct)))
fmt.Println(string(bytes.TrimLeftFunc([]byte("1234go-gopher!567"), unicode.IsNumber)))
}Output:
-gopher
go-gopher!
go-gopher!567func TrimPrefix
func TrimPrefix(s, prefix []byte) []byte
TrimPrefix возвращает s без предоставленной ведущей строки префикса. Если s не начинается с префикса, s возвращается без изменений.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
var b = []byte("Goodbye,, world!")
b = bytes.TrimPrefix(b, []byte("Goodbye,"))
b = bytes.TrimPrefix(b, []byte("See ya,"))
fmt.Printf("Hello%s", b)
}Output:
Hello, world!func TrimRight
func TrimRight(s []байт, cutset string) []байт
TrimRight возвращает подфрагмент s, отрезая все кодовые точки в кодировке UTF-8, которые содержатся в cutset.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Print(string(bytes.TrimRight([]byte("453gopher8257"), "0123456789")))
}
Output:
453gopherfunc TrimRightFunc
func TrimRightFunc(s []byte, f func(r rune) bool) []byte
TrimRightFunc возвращает подфрагмент s, отсекая все кодовые точки c, которые удовлетворяют f(c).
Пример
package main
import (
"bytes"
"fmt"
"unicode"
)
func main() {
fmt.Println(string(bytes.TrimRightFunc([]byte("go-gopher"), unicode.IsLetter)))
fmt.Println(string(bytes.TrimRightFunc([]byte("go-gopher!"), unicode.IsPunct)))
fmt.Println(string(bytes.TrimRightFunc([]byte("1234go-gopher!567"), unicode.IsNumber)))
}
Output:
go-
go-gopher
1234go-gopher!func TrimSpace
func TrimSpace(s []byte) []byte
TrimSpace возвращает подфрагмент s, отсекая все ведущие и последующие белые пробелы, как определено Unicode.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Printf("%s", bytes.TrimSpace([]byte(" \t\n a lone gopher \n\t\r\n")))
}
Output:
a lone gopherfunc TrimSuffix
func TrimSuffix(s, suffix []byte) []byte
TrimSuffix возвращает s без предоставленной строки суффикса. Если s не заканчивается суффиксом, s возвращается без изменений.
Пример
package main
import (
"bytes"
"os"
)
func main() {
var b = []byte("Hello, goodbye, etc!")
b = bytes.TrimSuffix(b, []byte("goodbye, etc!"))
b = bytes.TrimSuffix(b, []byte("gopher"))
b = append(b, bytes.TrimSuffix([]byte("world!"), []byte("x!"))...)
os.Stdout.Write(b)
}
Output:
Hello, world!Типы
type Buffer
type Buffer struct {
// содержит отфильтрованные или неэкспонированные поля
}
Буфер - это буфер переменного размера из байтов с методами Buffer.Read и Buffer.Write. Нулевое значение для Buffer - это пустой буфер, готовый к использованию.
Пример
package main
import (
"bytes"
"fmt"
"os"
)
func main() {
var b bytes.Buffer // A Buffer needs no initialization.
b.Write([]byte("Hello "))
fmt.Fprintf(&b, "world!")
b.WriteTo(os.Stdout)
}
Output:
Hello world!Пример Reader
package main
import (
"bytes"
"encoding/base64"
"io"
"os"
)
func main() {
// A Buffer can turn a string or a []byte into an io.Reader.
buf := bytes.NewBufferString("R29waGVycyBydWxlIQ==")
dec := base64.NewDecoder(base64.StdEncoding, buf)
io.Copy(os.Stdout, dec)
}
Output:
Gophers rule!func NewBuffer
func NewBuffer(buf []byte) *Buffer
NewBuffer создает и инициализирует новый буфер, используя buf в качестве его начального содержимого. Новый буфер получает право собственности на buf, и вызывающая сторона не должна использовать buf после этого вызова. NewBuffer предназначен для подготовки буфера к чтению существующих данных. Он также может быть использован для установки начального размера внутреннего буфера для записи. Для этого buf должен иметь желаемый объем, но длину, равную нулю.
В большинстве случаев для инициализации буфера достаточно использовать new(Buffer) (или просто объявить переменную Buffer).
func NewBufferString
func NewBufferString(s string) *Buffer
NewBufferString создает и инициализирует новый буфер, используя строку s в качестве его начального содержимого. Он предназначен для подготовки буфера к чтению существующей строки.
В большинстве случаев для инициализации буфера достаточно использовать new(Buffer) (или просто объявить переменную Buffer).
func (*Buffer) Available
func (b *Buffer) Available() int
Available возвращает количество неиспользованных байт в буфере.
func (*Buffer) AvailableBuffer
func (b *Buffer) AvailableBuffer() []byte
AvailableBuffer возвращает пустой буфер с емкостью b.Available(). Этот буфер предназначен для
Пример
package main
import (
"bytes"
"os"
"strconv"
)
func main() {
var buf bytes.Buffer
for i := 0; i < 4; i++ {
b := buf.AvailableBuffer()
b = strconv.AppendInt(b, int64(i), 10)
b = append(b, ' ')
buf.Write(b)
}
os.Stdout.Write(buf.Bytes())
}
Output:
0 1 2 3func (*Buffer) Bytes
func (b *Buffer) Bytes() []byte
Bytes возвращает фрагмент длины b.Len(), содержащий непрочитанную часть буфера. Этот фрагмент действителен для использования только до следующего изменения буфера (то есть только до следующего вызова метода типа Buffer.Read, Buffer.Write, Buffer.Reset или Buffer.Truncate). Слайс псевдоним содержимого буфера, по крайней мере, до следующей модификации буфера, поэтому немедленные изменения в слайсе повлияют на результат последующих чтений.
Пример
package main
import (
"bytes"
"os"
)
func main() {
buf := bytes.Buffer{}
buf.Write([]byte{'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'})
os.Stdout.Write(buf.Bytes())
}
Output:
hello worldfunc (*Buffer) Cap
func (b *Buffer) Cap() int
Cap возвращает емкость базового байтового среза буфера, то есть общее пространство, выделенное для данных буфера.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
buf1 := bytes.NewBuffer(make([]byte, 10))
buf2 := bytes.NewBuffer(make([]byte, 0, 10))
fmt.Println(buf1.Cap())
fmt.Println(buf2.Cap())
}Output:
10
10func (*Buffer) Grow
func (b *Buffer) Grow(n int)
Grow увеличивает емкость буфера, если необходимо, чтобы гарантировать место для еще n байт. После Grow(n) в буфер может быть записано не менее n байт без дополнительного выделения. Если n отрицательно, Grow паникует. Если буфер не может вырасти, то произойдет паника с ошибкой ErrTooLarge.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
var b bytes.Buffer
b.Grow(64)
bb := b.Bytes()
b.Write([]byte("64 bytes or fewer"))
fmt.Printf("%q", bb[:b.Len()])
}
Output:
"64 bytes or fewer"func (*Buffer) Len
func (b *Buffer) Len() int
Len возвращает количество байт непрочитанной части буфера; b.Len() == len(b.Bytes()).
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
var b bytes.Buffer
b.Grow(64)
b.Write([]byte("abcde"))
fmt.Printf("%d", b.Len())
}Output:
5func (*Buffer) Next
func (b *Buffer) Next(n int) []byte
Next возвращает фрагмент, содержащий следующие n байт из буфера, продвигая буфер так, как если бы байты были возвращены функцией Buffer.Read. Если в буфере меньше n байт, Next возвращает весь буфер. Фрагмент действителен только до следующего вызова метода чтения или записи.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
var b bytes.Buffer
b.Grow(64)
b.Write([]byte("abcde"))
fmt.Printf("%s\n", b.Next(2))
fmt.Printf("%s\n", b.Next(2))
fmt.Printf("%s", b.Next(2))
}
Output:
ab
cd
efunc (*Buffer) Read
func (b *Buffer) Read(p []byte) (n int, err error)
Read считывает следующие len(p) байт из буфера или пока буфер не будет исчерпан. Возвращаемое значение n - количество прочитанных байт. Если в буфере нет данных для возврата, err будет io.EOF (если только len(p) не равно нулю); в противном случае это будет nil.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
var b bytes.Buffer
b.Grow(64)
b.Write([]byte("abcde"))
rdbuf := make([]byte, 1)
n, err := b.Read(rdbuf)
if err != nil {
panic(err)
}
fmt.Println(n)
fmt.Println(b.String())
fmt.Println(string(rdbuf))
}Output:
1
bcde
afunc (*Buffer) ReadByte
func (b *Buffer) ReadByte() (byte, error)
ReadByte считывает и возвращает следующий байт из буфера. Если ни один байт не доступен, возвращается ошибка io.EOF.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
var b bytes.Buffer
b.Grow(64)
b.Write([]byte("abcde"))
c, err := b.ReadByte()
if err != nil {
panic(err)
}
fmt.Println(c)
fmt.Println(b.String())
}
Output:
97
bcdefunc (*Buffer) ReadBytes
func (b *Buffer) ReadBytes(delim byte) (line []byte, err error)
ReadBytes читает до первого появления delim во входных данных, возвращая фрагмент, содержащий данные до разделителя включительно. Если ReadBytes встречает ошибку до нахождения разделителя, он возвращает данные, прочитанные до ошибки, и саму ошибку (часто io.EOF). ReadBytes возвращает err != nil тогда и только тогда, когда возвращаемые данные не заканчиваются на delim.
func (*Buffer) ReadFrom
func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error)
ReadFrom читает данные из r до EOF и добавляет их в буфер, увеличивая буфер по мере необходимости. Возвращаемое значение n - это количество прочитанных байт. Также возвращается любая ошибка, кроме io.EOF, возникшая во время чтения. Если буфер становится слишком большим, ReadFrom паникует с сообщением ErrTooLarge.
func (*Buffer) ReadRune
func (b *Buffer) ReadRune() (r rune, size int, err error)
ReadRune считывает и возвращает следующую кодовую точку Юникода в кодировке UTF-8 из буфера. Если байтов нет, возвращается ошибка io.EOF. Если байты имеют ошибочную кодировку UTF-8, то считывается один байт и возвращается U+FFFD, 1.
func (*Buffer) ReadString
func (b *Buffer) ReadString(delim byte) (line string, err error)
ReadString читает до первого появления delim во входных данных, возвращая строку, содержащую данные до разделителя включительно. Если ReadString встречает ошибку до нахождения разделителя, она возвращает данные, считанные до ошибки, и саму ошибку (часто io.EOF). ReadString возвращает err != nil тогда и только тогда, когда возвращаемые данные не заканчиваются разделителем.
func (*Buffer) Reset
func (b *Buffer) Сброс()
Сброс приводит к тому, что буфер становится пустым, но сохраняет базовое хранилище для использования в будущих записях. Сброс - это то же самое, что и Buffer.Truncate(0).
func (*Buffer) String
func (b *Buffer) String() string
String возвращает содержимое непрочитанной части буфера в виде строки. Если буфер является нулевым указателем, возвращается «
Для более эффективного создания строк см. тип strings.Builder.
func (*Buffer) Truncate
func (b *Buffer) Truncate(n int)
Truncate удаляет из буфера все непрочитанные байты, кроме первых n, но продолжает использовать выделенную память. Если n отрицательно или больше длины буфера, происходит паника.
func (*Buffer) UnreadByte
func (b *Buffer) UnreadByte() error
UnreadByte считывает последний байт, возвращенный последней успешной операцией чтения, которая прочитала хотя бы один байт. Если с момента последнего чтения произошла запись, если последнее чтение вернуло ошибку или если при чтении было прочитано ноль байт, UnreadByte возвращает ошибку.
func (*Buffer) UnreadRune
func (b *Buffer) UnreadRune() error
UnreadRune считывает последнюю руну, возвращенную Buffer.ReadRune. Если последняя операция чтения или записи в буфер не была успешной Buffer.ReadRune, UnreadRune возвращает ошибку. (В этом отношении она строже, чем Buffer.UnreadByte, которая отменяет чтение последнего байта из любой операции чтения).
func (*Buffer) Write
func (b *Buffer) Write(p []byte) (n int, err error)
Write добавляет содержимое p в буфер, увеличивая буфер по мере необходимости. Возвращаемое значение n - длина p; err всегда равно nil. Если буфер становится слишком большим, Write паникует с сообщением ErrTooLarge.
func (*Buffer) WriteByte
func (b *Buffer) WriteByte(c byte) error
WriteByte добавляет байт c в буфер, увеличивая буфер по мере необходимости. Возвращаемая ошибка всегда равна nil, но она включается, чтобы соответствовать WriteByte от bufio.Writer. Если буфер становится слишком большим, WriteByte паникует с сообщением ErrTooLarge.
func (*Buffer) WriteRune
func (b *Buffer) WriteRune(r rune) (n int, err error)
WriteRune добавляет кодировку UTF-8 кодовой точки Юникода r к буферу, возвращая его длину и ошибку, которая всегда равна nil, но включается для соответствия WriteRune от bufio.Writer. Буфер увеличивается по мере необходимости; если он становится слишком большим, WriteRune паникует с сообщением ErrTooLarge.
func (*Buffer) WriteString
func (b *Buffer) WriteString(s string) (n int, err error)
WriteString добавляет содержимое s в буфер, увеличивая буфер по мере необходимости. Возвращаемое значение n - длина s; err всегда равно nil. Если буфер становится слишком большим, WriteString паникует с сообщением ErrTooLarge.
func (*Buffer) WriteTo
func (b *Buffer) WriteTo(w io.Writer) (n int64, err error)
WriteTo записывает данные в w до тех пор, пока буфер не будет исчерпан или не произойдет ошибка. Возвращаемое значение n - это количество записанных байт; оно всегда помещается в int, но для соответствия интерфейсу io.WriterTo оно равно int64. Также возвращается любая ошибка, возникшая во время записи.
type Reader
type Reader struct {
// содержит отфильтрованные или неотфильтрованные поля
}
Reader реализует интерфейсы io.Reader, io.ReaderAt, io.WriterTo, io.Seeker, io.ByteScanner и io.RuneScanner, читая из байтового фрагмента. В отличие от буфера, Reader доступен только для чтения и поддерживает поиск. Нулевое значение для Reader работает как Reader пустого фрагмента.
func NewReader
func NewReader(b []byte) *Reader
NewReader возвращает новый Reader, читающий из b.
func (*Reader) Len
func (r *Reader) Len() int
Len возвращает количество байт непрочитанной части фрагмента.
Пример
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Println(bytes.NewReader([]byte("Hi!")).Len())
fmt.Println(bytes.NewReader([]byte("こんにちは!")).Len())
}Output:
3
16func (*Reader) Read
func (r *Reader) Read(b []byte) (n int, err error)
Read реализует интерфейс io.Reader.
func (*Reader) ReadAt
func (r *Reader) ReadAt(b []byte, off int64) (n int, err error)
ReadAt реализует интерфейс io.ReaderAt.
func (*Reader) ReadByte
func (r *Reader) ReadByte() (byte, error)
ReadByte реализует интерфейс io.ByteReader.
func (*Reader) ReadRune
func (r *Reader) ReadRune() (ch rune, size int, err error)
ReadRune реализует интерфейс io.RuneReader.
func (*Reader) Reset
func (r *Reader) Reset(b []byte)
Сброс переводит ридер в режим чтения из b.
func (*Reader) Seek
func (r *Reader) Seek(offset int64, whence int) (int64, error)
Seek реализует интерфейс io.Seeker.
func (*Reader) Size
func (r *Reader) Size() int64
Size возвращает исходную длину базового байтового фрагмента. Size - это количество байт, доступных для чтения через Reader.ReadAt. На результат не влияют никакие вызовы методов, кроме Reader.Reset.
func (*Reader) UnreadByte
func (r *Reader) UnreadByte() error
UnreadByte дополняет Reader.ReadByte в реализации интерфейса io.ByteScanner.
func (*Reader) UnreadRune
func (r *Reader) UnreadRune() error
UnreadRune дополняет Reader.ReadRune в реализации интерфейса io.RuneScanner.
func (*Reader) WriteTo
func (r *Reader) WriteTo(w io.Writer) (n int64, err error)
WriteTo реализует интерфейс io.WriterTo.
6 - Описание пакета database языка программирования Go
Пакет sql предоставляет общий интерфейс для баз данных SQL (или SQL-подобных).
Пакет sql должен использоваться вместе с драйвером базы данных. Список драйверов см. на сайте https://golang.org/s/sqldrivers.
Драйверы, не поддерживающие отмену контекста, вернут результат только после завершения запроса.
Примеры использования приведены на вики-странице https://golang.org/s/sqlwiki.
Пакет driver определяет интерфейсы, которые должны быть реализованы драйверами баз данных, используемыми пакетом sql.
Большая часть кода должна использовать пакет database/sql.
Подробная документация по драйверам: https://pkg.go.dev/database/sql/driver
6.1 - Работа с пакетом database/sql в Go: ошибки и их обработка
Пакет database/sql в Go предоставляет универсальный интерфейс для работы с SQL-базами данных. Рассмотрим основные ошибки, которые могут возникнуть при работе с этим пакетом, и как их правильно обрабатывать.
Основные ошибки пакета database/sql
1. ErrConnDone - соединение уже закрыто
var ErrConnDone = errors.New("sql: connection is already closed")
Эта ошибка возникает, когда вы пытаетесь выполнить операцию на соединении, которое уже было возвращено в пул соединений.
Пример:
db, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
conn, err := db.Conn(context.Background())
if err != nil {
log.Fatal(err)
}
// Возвращаем соединение в пул
conn.Close()
// Пытаемся использовать закрытое соединение
err = conn.PingContext(context.Background())
if errors.Is(err, sql.ErrConnDone) {
fmt.Println("Ошибка: соединение уже закрыто")
}
2. ErrNoRows - нет строк в результате
var ErrNoRows = errors.New("sql: no rows in result set")
Эта ошибка возвращается методом Scan, когда QueryRow не находит ни одной строки.
Правильная обработка:
var name string
err := db.QueryRow("SELECT name FROM users WHERE id = ?", 123).Scan(&name)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
fmt.Println("Пользователь не найден")
} else {
log.Fatal(err)
}
} else {
fmt.Printf("Имя пользователя: %s\n", name)
}
3. ErrTxDone - транзакция уже завершена
var ErrTxDone = errors.New("sql: transaction has already been committed or rolled back")
Эта ошибка возникает при попытке выполнить операцию в уже завершенной транзакции.
Пример:
tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
// Фиксируем транзакцию
err = tx.Commit()
if err != nil {
log.Fatal(err)
}
// Пытаемся выполнить запрос в завершенной транзакции
_, err = tx.Exec("INSERT INTO users(name) VALUES (?)", "Alice")
if errors.Is(err, sql.ErrTxDone) {
fmt.Println("Ошибка: транзакция уже завершена")
}
Советы по работе с database/sql
- Всегда проверяйте ошибки после операций с базой данных.
- Используйте
errors.Isдля проверки конкретных ошибок пакета database/sql. - Закрывайте ресурсы (соединения, транзакции, результаты запросов) с помощью defer.
- Используйте контексты для управления таймаутами и отменой операций.
Полный пример работы с базой данных:
package main
import (
"context"
"database/sql"
"errors"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
type User struct {
ID int
Name string
}
func main() {
db, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Проверка соединения
err = db.Ping()
if err != nil {
log.Fatal(err)
}
// Пример запроса с обработкой ErrNoRows
user, err := getUser(db, 123)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
fmt.Println("Пользователь не найден")
} else {
log.Fatal(err)
}
} else {
fmt.Printf("Найден пользователь: %+v\n", user)
}
// Пример транзакции
err = transferMoney(db, 1, 2, 100)
if err != nil {
log.Fatal(err)
}
}
func getUser(db *sql.DB, id int) (*User, error) {
var user User
err := db.QueryRow("SELECT id, name FROM users WHERE id = ?", id).Scan(&user.ID, &user.Name)
if err != nil {
return nil, err
}
return &user, nil
}
func transferMoney(db *sql.DB, from, to, amount int) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
if err != nil {
tx.Rollback()
return
}
err = tx.Commit()
}()
// Списание денег
_, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, from)
if err != nil {
return err
}
// Зачисление денег
_, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, to)
if err != nil {
return err
}
return nil
}
6.2 - Подробное описание функций пакета database/sql в Go
Основные функции управления драйверами
func Drivers() []string
Эта функция возвращает отсортированный список имен зарегистрированных драйверов баз данных.
Пример использования:
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql" // регистрируем MySQL драйвер
_ "github.com/lib/pq" // регистрируем PostgreSQL драйвер
)
func main() {
// Получаем список зарегистрированных драйверов
drivers := sql.Drivers()
fmt.Println("Зарегистрированные драйверы:")
for _, driver := range drivers {
fmt.Println("-", driver)
}
// Выведет что-то вроде:
// Зарегистрированные драйверы:
// - mysql
// - postgres
}
func Register(name string, driver driver.Driver)
Эта функция регистрирует драйвер базы данных под указанным именем. Если попытаться зарегистрировать два драйвера с одинаковым именем или передать nil, функция вызовет panic.
Обычно драйверы регистрируют себя автоматически при импорте с пустым идентификатором _, как в примере выше. Но можно регистрировать драйверы и вручную:
package main
import (
"database/sql"
"database/sql/driver"
"fmt"
)
// Простой mock-драйвер для примера
type mockDriver struct{}
func (d *mockDriver) Open(name string) (driver.Conn, error) {
fmt.Println("Mock driver открывает соединение с:", name)
return nil, nil
}
func main() {
// Регистрируем наш mock-драйвер
sql.Register("mock", &mockDriver{})
// Теперь можем его использовать
db, err := sql.Open("mock", "test-connection")
if err != nil {
fmt.Println("Ошибка:", err)
return
}
defer db.Close()
// Проверяем, что драйвер зарегистрирован
fmt.Println("Зарегистрированные драйверы:", sql.Drivers())
}
Основные функции для работы с БД
func Open(driverName, dataSourceName string) (*DB, error)
Открывает новое соединение с базой данных. На самом деле не устанавливает соединение сразу, а только готовит объект DB.
Пример:
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
func (*DB) Ping() error и func (*DB) PingContext(ctx context.Context) error
Проверяет, что соединение с БД живо и доступно.
Пример:
err = db.Ping()
if err != nil {
log.Fatal("Не удалось подключиться к БД:", err)
}
fmt.Println("Успешное подключение к БД!")
Функции выполнения запросов
func (*DB) Exec(query string, args ...interface{}) (Result, error)
func (*DB) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
Выполняет запрос без возврата строк (INSERT, UPDATE, DELETE).
Пример:
result, err := db.Exec(
"INSERT INTO users(name, age) VALUES (?, ?)",
"Alice",
30,
)
if err != nil {
log.Fatal(err)
}
lastID, err := result.LastInsertId()
rowsAffected, err := result.RowsAffected()
func (*DB) Query(query string, args ...interface{}) (*Rows, error)
func (*DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)
Выполняет запрос, возвращающий строки (SELECT).
Пример:
rows, err := db.Query("SELECT id, name, age FROM users WHERE age > ?", 25)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var id int
var name string
var age int
err = rows.Scan(&id, &name, &age)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%d: %s, %d\n", id, name, age)
}
if err = rows.Err(); err != nil {
log.Fatal(err)
}
func (*DB) QueryRow(query string, args ...interface{}) *Row
func (*DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row
Выполняет запрос, который должен вернуть не более одной строки.
Пример:
var name string
var age int
err := db.QueryRow("SELECT name, age FROM users WHERE id = ?", 1).Scan(&name, &age)
if err != nil {
if err == sql.ErrNoRows {
fmt.Println("Пользователь не найден")
} else {
log.Fatal(err)
}
} else {
fmt.Printf("Имя: %s, Возраст: %d\n", name, age)
}
Функции для работы с транзакциями
func (*DB) Begin() (*Tx, error)
func (*DB) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error)
Начинает новую транзакцию.
Пример:
tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
// Откатываем транзакцию в случае ошибки
defer func() {
if err != nil {
tx.Rollback()
}
}()
_, err = tx.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
if err != nil {
return err
}
_, err = tx.Exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
if err != nil {
return err
}
// Если все успешно - коммитим
err = tx.Commit()
if err != nil {
return err
}
Работа с соединениями
func (*DB) Conn(ctx context.Context) (*Conn, error)
Получает одно соединение из пула для выполнения нескольких операций в одном контексте.
Пример:
conn, err := db.Conn(context.Background())
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// Выполняем несколько операций в одном соединении
var count int
err = conn.QueryRowContext(context.Background(), "SELECT COUNT(*) FROM users").Scan(&count)
if err != nil {
log.Fatal(err)
}
_, err = conn.ExecContext(context.Background(), "UPDATE stats SET user_count = ?", count)
if err != nil {
log.Fatal(err)
}
Заключение
Пакет database/sql предоставляет все необходимые функции для работы с SQL-базами данных в Go. Основные принципы:
- Всегда проверяйте ошибки
- Закрывайте ресурсы (Rows, Tx, Conn) с помощью defer
- Используйте контексты для управления таймаутами
- Для разных типов запросов используйте соответствующие методы (Exec, Query, QueryRow)
Правильное использование этих функций позволит вам создавать надежные и эффективные приложения, работающие с базами данных.
6.3 - Контекст (context) в транзакциях database/sql
Основные цели использования контекста в транзакциях
- Отмена операций - можно прервать долгий запрос
- Таймауты - установка максимального времени выполнения
- Распространение значений - передача метаданных через цепочку вызовов
Методы с поддержкой контекста
В database/sql большинство операций имеют две версии:
- Обычная (
Begin,Exec,Queryи т.д.) - С контекстом (
BeginTx,ExecContext,QueryContextи т.д.)
Пример использования контекста с таймаутом
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
// Начинаем транзакцию с таймаутом
tx, err := db.BeginTx(ctx, nil)
if err != nil {
log.Fatal(err)
}
defer tx.Rollback() // Безопасный откат при ошибках
// Выполняем запрос в транзакции
_, err = tx.ExecContext(ctx, "UPDATE accounts SET balance = balance - 100 WHERE id = 1")
if err != nil {
// Если контекст истек, получим context.DeadlineExceeded
log.Printf("Update failed: %v", err)
return
}
// Коммитим транзакцию
err = tx.Commit()
if err != nil {
log.Printf("Commit failed: %v", err)
}
Особенности работы контекста в транзакциях
- Каскадная отмена - отмена контекста прерывает все операции в транзакции
- Изоляция транзакций - контекст не влияет на другие транзакции
- Ресурсы - отмена не освобождает соединение автоматически
Практические сценарии использования
- HTTP-обработчики - привязка к времени жизни запроса:
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
tx, err := db.BeginTx(ctx, nil)
// ...
}
- Долгие отчеты - возможность отмены:
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(10*time.Second)
cancel() // Принудительная отмена через 10 сек
}()
rows, err := db.QueryContext(ctx, "SELECT * FROM big_table")
- Распределенные транзакции - передача идентификаторов:
ctx := context.WithValue(context.Background(), "txID", generateID())
tx, _ := db.BeginTx(ctx, nil)
Важные нюансы
- Всегда проверяйте ошибки на
context.Canceledиcontext.DeadlineExceeded - Освобождайте ресурсы с помощью
deferдаже при отмене контекста - Не передавайте один контекст в несколько независимых транзакций
Использование контекста делает ваши транзакции более управляемыми и устойчивыми к долгим операциям.
6.4 - Описание типа database/sql DB
type DB struct {
// содержит отфильтрованные или неэкспонированные поля
}
Пакет sql создает и освобождает соединения автоматически; он также поддерживает свободный пул незадействованных соединений. Если в базе данных есть понятие состояния каждого соединения, то такое состояние можно надежно наблюдать в рамках транзакции (Tx) или соединения (Conn). После вызова DB.Begin возвращаемая Tx привязывается к одному соединению. После вызова Tx.Commit или Tx.Rollback для транзакции, соединение этой транзакции возвращается в пул незанятых соединений DB. Размер пула можно контролировать с помощью DB.SetMaxIdleConns.
func Open
func Open(driverName, dataSourceName string) (*DB, error)
Open открывает базу данных, указанную именем драйвера базы данных и именем источника данных, обычно состоящим как минимум из имени базы данных и информации о подключении.
Большинство пользователей открывают базу данных с помощью специфической для драйвера вспомогательной функции подключения, которая возвращает *DB. Драйверы баз данных не включены в стандартную библиотеку Go. Список драйверов сторонних разработчиков см. на https://golang.org/s/sqldrivers.
Open может просто проверить свои аргументы, не создавая соединения с базой данных. Чтобы убедиться, что имя источника данных действительно, вызовите DB.Ping.
Возвращаемая БД безопасна для одновременного использования несколькими горутинами и поддерживает собственный пул незанятых соединений. Таким образом, функцию Open следует вызывать только один раз. Редко возникает необходимость закрывать БД.
func OpenDB
func OpenDB(c driver.Connector) *DB
OpenDB открывает базу данных с помощью driver.Connector, позволяя драйверам обходить строковое имя источника данных.
Большинство пользователей открывают базу данных с помощью специфической для драйвера функции-помощника подключения, которая возвращает *DB. Драйверы баз данных не включены в стандартную библиотеку Go.
OpenDB может просто проверить свои аргументы, не создавая соединения с базой данных. Чтобы убедиться, что имя источника данных действительно, вызовите DB.Ping.
Возвращаемая БД безопасна для одновременного использования несколькими горутинами и поддерживает свой собственный пул незанятых соединений. Таким образом, функция OpenDB должна быть вызвана только один раз. Редко возникает необходимость закрывать БД.
func (*DB) Close
func (db *DB) Close() error
Close закрывает базу данных и предотвращает запуск новых запросов. Затем Close ожидает завершения всех запросов, которые начали обрабатываться на сервере.
Закрывать БД приходится редко, так как хэндл БД должен быть долгоживущим и использоваться совместно многими горутинами.
db, err := sql.Open("mysql", dsn)
if err != nil {
log.Fatal(err)
}
defer db.Close() // Всегда закрывайте соединение
func (*DB) Ping
func (db *DB) Ping() error
Ping проверяет, что соединение с базой данных еще живо, и при необходимости устанавливает соединение.
Внутри Ping использует context.Background; чтобы указать контекст, используйте DB.PingContext.
err = db.Ping()
if err != nil {
log.Fatal("Connection failed:", err)
}
func (*DB) PingContext
func (db *DB) PingContext(ctx context.Context) error
PingContext проверяет, что соединение с базой данных еще живо, и при необходимости устанавливает соединение.
Пример
package main
import (
"context"
"database/sql"
"log"
"time"
)
var (
ctx context.Context
db *sql.DB
)
func main() {
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel()
status := "up"
if err := db.PingContext(ctx); err != nil {
status = "down"
}
log.Println(status)
}
func (*DB) Begin
func (db *DB) Begin() (*Tx, error)
Begin начинает транзакцию. Уровень изоляции по умолчанию зависит от драйвера.
Begin внутренне использует context.Background; чтобы указать контекст, используйте DB.BeginTx.
func (*DB) BeginTx
func (db *DB) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error)
BeginTx начинает транзакцию.
Предоставленный контекст используется до тех пор, пока транзакция не будет зафиксирована или откачена. Если контекст будет отменен, пакет sql откатит транзакцию. Tx.Commit вернет ошибку, если контекст, предоставленный BeginTx, будет отменен.
Предоставленный параметр TxOptions является необязательным и может быть равен nil, если следует использовать значения по умолчанию. Если используется уровень изоляции не по умолчанию, который драйвер не поддерживает, будет возвращена ошибка.
Пример
import (
"context"
"database/sql"
"log"
)
var (
ctx context.Context
db *sql.DB
)
func main() {
tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
if err != nil {
log.Fatal(err)
}
id := 37
_, execErr := tx.Exec(`UPDATE users SET status = ? WHERE id = ?`, "paid", id)
if execErr != nil {
_ = tx.Rollback()
log.Fatal(execErr)
}
if err := tx.Commit(); err != nil {
log.Fatal(err)
}
}
func (*DB) Conn
func (db *DB) Conn(ctx context.Context) (*Conn, error)
Conn возвращает одно соединение, либо открывая новое соединение, либо возвращая существующее соединение из пула соединений. Conn будет блокироваться до тех пор, пока не будет возвращено соединение или не будет отменен ctx. Запросы, выполняемые по одному и тому же Conn, будут выполняться в одной и той же сессии базы данных.
Каждый Conn должен быть возвращен в пул баз данных после использования вызовом Conn.Close.
Подробнее о Conn
Работа с соединениями (Conn) в database/sql
Соединение (Conn) представляет собой одиночное подключение к базе данных, которое можно использовать как в уже открытом пуле соединений (DB), так и для прямого управления подключением.
Основные концепции
Отличие от
DB:DB- это пул соединений (управляет множеством подключений)Conn- одно конкретное соединение
Когда использовать:
- Когда нужно выполнить несколько операций в рамках одного соединения
- Для работы с сессионными переменными
- Для транзакций, требующих изоляции от других операций
Примеры использования
1. Получение соединения из пула
package main
import (
"context"
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Получаем отдельное соединение из пула
conn, err := db.Conn(context.Background())
if err != nil {
log.Fatal(err)
}
defer conn.Close() // Важно возвращать соединение в пул
// Используем соединение
var result int
err = conn.QueryRowContext(context.Background(), "SELECT 1 + 1").Scan(&result)
if err != nil {
log.Fatal(err)
}
fmt.Println("Result:", result)
}
2. Использование сессионных переменных
// Устанавливаем переменную сессии и используем её в запросе
err = conn.ExecContext(context.Background(), "SET @user_id = 123").Scan(&result)
if err != nil {
log.Fatal(err)
}
var userID int
err = conn.QueryRowContext(context.Background(), "SELECT @user_id").Scan(&userID)
fmt.Println("User ID:", userID) // Выведет: User ID: 123
3. Транзакции в отдельном соединении
// Начинаем транзакцию в этом соединении
tx, err := conn.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelSerializable})
if err != nil {
log.Fatal(err)
}
// Выполняем операции в транзакции
_, err = tx.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
_, err = tx.Exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
if err != nil {
tx.Rollback()
log.Fatal(err)
} else {
tx.Commit()
}
4. Прямое подключение (без пула)
Для прямого подключения без пула можно использовать драйвер напрямую, но это не рекомендуется:
package main
import (
"database/sql"
"database/sql/driver"
"fmt"
"log"
)
func main() {
// Получаем драйвер по имени
driver := sql.Driver(new(mysql.MySQLDriver))
// Создаем прямое соединение (не рекомендуется для production)
conn, err := driver.Open("user:password@tcp(localhost:3306)/dbname")
if err != nil {
log.Fatal(err)
}
defer conn.Close() // Закрываем соединение
// Преобразуем в тип Conn (если нужно)
if sqlConn, ok := conn.(driver.Conn); ok {
// Работаем с соединением...
}
}
Когда использовать Conn вместо DB?
- Сессионные переменные: Когда нужно сохранить состояние между запросами
- Временные таблицы: Которые видны только в текущем соединении
- LOCK TABLES: Когда нужно заблокировать таблицы на время сессии
- Точный контроль: Когда важно, чтобы несколько операций использовали одно соединение
Важные замечания
- Всегда закрывайте
Connс помощьюdefer conn.Close() - Не держите соединения долго - возвращайте их в пул
- Для большинства случаев достаточно стандартного
DB Connдороже в создании, чем операции черезDB
Пример комплексного использования:
func complexOperation(db *sql.DB) error {
// Получаем соединение
conn, err := db.Conn(context.Background())
if err != nil {
return err
}
defer conn.Close()
// Настраиваем сессию
if _, err := conn.ExecContext(context.Background(), "SET @start_time = NOW()"); err != nil {
return err
}
// Выполняем транзакцию
tx, err := conn.BeginTx(context.Background(), nil)
if err != nil {
return err
}
// Бизнес-логика...
if _, err := tx.Exec("INSERT INTO orders (...) VALUES (...)"); err != nil {
tx.Rollback()
return err
}
// Получаем время выполнения
var startTime string
conn.QueryRowContext(context.Background(), "SELECT @start_time").Scan(&startTime)
fmt.Println("Operation started at:", startTime)
return tx.Commit()
}
Используйте Conn осознанно, только когда действительно нужно контролировать конкретное соединение. В большинстве случаев достаточно стандартного DB.
func (*DB) Driver
func (db *DB) Driver() driver.Driver
Driver возвращает базовый драйвер базы данных.
func (*DB) Exec
func (db *DB) Exec(query string, args ...any) (Result, error)
Exec выполняет запрос, не возвращая ни одной строки. В качестве args используются любые параметры-заполнители в запросе.
Exec использует внутренний контекст context.Background; чтобы указать контекст, используйте DB.ExecContext.
context.Background
func Background() ContextBackground возвращает ненулевой, пустой Context. Он никогда не отменяется, не имеет значений и сроков. Обычно используется главной функцией, инициализацией и тестами, а также в качестве контекста верхнего уровня для входящих запросов.
func (*DB) ExecContext
func (db *DB) ExecContext(ctx context.Context, query string, args ...any) (Result, error)
ExecContext выполняет запрос, не возвращая ни одной строки. В качестве args используются любые параметры-заполнители в запросе.
func (*DB) Prepare
func (db *DB) Prepare(query string) (*Stmt, error)
Prepare создает подготовленный отчет для последующих запросов или выполнения. Из полученного отчета можно одновременно выполнять несколько запросов или операций. Вызывающая сторона должна вызвать метод *Stmt.Close оператора, когда он больше не нужен.
Prepare внутренне использует context.Background; чтобы указать контекст, используйте DB.PrepareContext.
Пример
import (
"context"
"database/sql"
"log"
)
var db *sql.DB
func main() {
projects := []struct {
mascot string
release int
}{
{"tux", 1991},
{"duke", 1996},
{"gopher", 2009},
{"moby dock", 2013},
}
stmt, err := db.Prepare("INSERT INTO projects(id, mascot, release, category) VALUES( ?, ?, ?, ? )")
if err != nil {
log.Fatal(err)
}
defer stmt.Close() // Prepared statements take up server resources and should be closed after use.
for id, project := range projects {
if _, err := stmt.Exec(id+1, project.mascot, project.release, "open source"); err != nil {
log.Fatal(err)
}
}
}
func (*DB) PrepareContext
func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error)
PrepareContext создает подготовленный запрос для последующих запросов или выполнений. Из полученного отчета можно одновременно выполнять несколько запросов или операций. Вызывающая сторона должна вызвать метод *Stmt.Close оператора, когда он больше не нужен.
Предоставленный контекст используется для подготовки утверждения, а не для его выполнения.
func (*DB) Query
func (db *DB) Query(query string, args ...any) (*Rows, error)
Query выполняет запрос, возвращающий строки, обычно SELECT. В качестве args используются любые параметры-заполнители в запросе.
Query внутренне использует context.Background; чтобы указать контекст, используйте DB.QueryContext.
Пример
package main
import (
"context"
"database/sql"
"log"
_ "github.com/lib/pq" // или другой драйвер БД
)
var db *sql.DB
func main() {
// Инициализация подключения к БД
var err error
db, err = sql.Open("postgres", "user=postgres dbname=test sslmode=disable") // замените на свои параметры
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
defer db.Close()
// Проверка соединения
if err := db.Ping(); err != nil {
log.Fatal("Failed to ping database:", err)
}
age := 27
q := `
-- Создаем временную таблицу
CREATE TEMP TABLE uid (id bigint) ON COMMIT DROP;
-- Заполняем временную таблицу
INSERT INTO uid
SELECT id FROM users WHERE age < $1;
-- Первый набор результатов
SELECT
users.id, name
FROM
users
JOIN uid ON users.id = uid.id;
-- Второй набор результатов
SELECT
ur.user_id, ur.role_id
FROM
user_roles AS ur
JOIN uid ON uid.id = ur.user_id;
`
// Используем контекст для запроса
ctx := context.Background()
rows, err := db.QueryContext(ctx, q, age)
if err != nil {
log.Fatal("Query failed:", err)
}
defer rows.Close()
// Обрабатываем первый набор результатов
log.Println("Processing users:")
for rows.Next() {
var (
id int64
name string
)
if err := rows.Scan(&id, &name); err != nil {
log.Fatal("Failed to scan user row:", err)
}
log.Printf("id %d name is %s\n", id, name)
}
// Переходим ко второму набору результатов
if !rows.NextResultSet() {
if err := rows.Err(); err != nil {
log.Fatal("Failed to get next result set:", err)
}
log.Fatal("Expected more result sets, but none available")
}
// Обрабатываем второй набор результатов
roleMap := map[int64]string{
1: "user",
2: "admin",
3: "gopher",
}
log.Println("\nProcessing roles:")
for rows.Next() {
var (
userID int64
roleID int64
)
if err := rows.Scan(&userID, &roleID); err != nil {
log.Fatal("Failed to scan role row:", err)
}
roleName, ok := roleMap[roleID]
if !ok {
roleName = "unknown"
}
log.Printf("user %d has role %s\n", userID, roleName)
}
if err := rows.Err(); err != nil {
log.Fatal("Rows error:", err)
}
}
func (*DB) QueryContext
func (db *DB) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error)
QueryContext выполняет запрос, возвращающий строки, обычно SELECT. В качестве args используются любые параметры-заполнители запроса.
Пример
package main
import (
"context"
"database/sql"
"fmt"
"log"
"strings"
_ "github.com/go-sql-driver/mysql" // или другой драйвер БД
)
func main() {
// 1. Инициализация подключения к БД
var err error
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
defer db.Close()
// 2. Проверка соединения
ctx := context.Background()
if err := db.PingContext(ctx); err != nil {
log.Fatal("Failed to ping database:", err)
}
// 3. Выполнение запроса
age := 27
rows, err := db.QueryContext(ctx, "SELECT name FROM users WHERE age=?", age)
if err != nil {
log.Fatal("Query failed:", err)
}
defer rows.Close()
// 4. Обработка результатов
names := make([]string, 0)
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
log.Printf("Scan error (skipping row): %v", err) // Не прерываем выполнение
continue
}
names = append(names, name)
}
// 5. Проверка ошибок после итерации
if err := rows.Err(); err != nil {
log.Fatal("Rows error:", err)
}
// 6. Проверка ошибок закрытия (более аккуратная обработка)
if err := rows.Close(); err != nil {
log.Printf("Warning: error closing rows: %v", err) // Не фатальная ошибка
}
// 7. Вывод результатов
if len(names) > 0 {
fmt.Printf("%s are %d years old\n", strings.Join(names, ", "), age)
} else {
fmt.Printf("No users found at age %d\n", age)
}
}
func (*DB) QueryRow
func (db *DB) QueryRow(query string, args ...any) *Row
QueryRow выполняет запрос, который, как ожидается, вернет не более одной строки. QueryRow всегда возвращает значение, не являющееся нулем. Ошибки откладываются до вызова метода Row’s Scan. Если запрос не выбрал ни одной строки, то *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.
QueryRow внутренне использует context.Background;
func (*DB) QueryRowContext
func (db *DB) QueryRowContext(ctx context.Context, query string, args ...any) *Row
QueryRowContext выполняет запрос, который, как ожидается, вернет не более одной строки. QueryRowContext всегда возвращает значение non-nil. Ошибки откладываются до вызова метода Row’s Scan. Если запрос не выбрал ни одной строки, то *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.
Пример
package main
import (
"context"
"database/sql"
"log"
"time"
_ "github.com/go-sql-driver/mysql" // Импорт драйвера БД
)
func main() {
// Инициализация подключения к БД
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
if err != nil {
log.Fatalf("Failed to connect to database: %v", err)
}
defer db.Close()
// Проверка соединения
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := db.PingContext(ctx); err != nil {
log.Fatalf("Failed to ping database: %v", err)
}
// Выполнение запроса
id := 123
var username string
var created time.Time
// Используем контекст с таймаутом для запроса
queryCtx, queryCancel := context.WithTimeout(context.Background(), 3*time.Second)
defer queryCancel()
err = db.QueryRowContext(queryCtx,
"SELECT username, created_at FROM users WHERE id=?",
id,
).Scan(&username, &created)
switch {
case err == sql.ErrNoRows:
log.Printf("No user found with id %d", id)
case err != nil:
log.Printf("Query error: %v", err) // Не используем Fatalf чтобы не прерывать программу
default:
log.Printf(
"Username: %q, Account created on: %s",
username,
created.Format("2006-01-02 15:04:05"),
)
}
}
func (*DB) SetConnMaxIdleTime
func (db *DB) SetConnMaxIdleTime(d time.Duration)
SetConnMaxIdleTime устанавливает максимальное время, в течение которого соединение может простаивать.
Просроченные соединения могут быть лениво закрыты перед повторным использованием.
Если d <= 0, соединения не закрываются из-за времени простоя соединения.
func (*DB) SetConnMaxLifetime
func (db *DB) SetConnMaxLifetime(d time.Duration)
SetConnMaxLifetime устанавливает максимальное количество времени, в течение которого соединение может быть использовано повторно.
Просроченные соединения могут быть лениво закрыты перед повторным использованием.
Если d <= 0, соединения не закрываются из-за возраста соединения.
func (*DB) SetMaxIdleConns
func (db *DB) SetMaxIdleConns(n int)
SetMaxIdleConns устанавливает максимальное количество соединений в пуле незадействованных соединений.
Если MaxOpenConns больше 0, но меньше нового MaxIdleConns, то новый MaxIdleConns будет уменьшен, чтобы соответствовать лимиту MaxOpenConns.
Если n <= 0, простаивающие соединения не сохраняются.
По умолчанию максимальное количество незадействованных соединений в настоящее время равно 2. Это может измениться в будущем выпуске.
func (*DB) SetMaxOpenConns
func (db *DB) SetMaxOpenConns(n int)
SetMaxOpenConns устанавливает максимальное количество открытых соединений с базой данных.
Если MaxIdleConns больше 0, а новое значение MaxOpenConns меньше MaxIdleConns, то MaxIdleConns будет уменьшено, чтобы соответствовать новому ограничению MaxOpenConns.
Если n <= 0, то количество открытых соединений не ограничено. По умолчанию 0 (неограниченно).
func (*DB) Stats
func (db *DB) Stats() DBStats
Stats возвращает статистику базы данных.
6.5 - Описание типа database/sql ColumnType
type ColumnType struct {
// содержит отфильтрованные или неэкспортированные поля
}
ColumnType содержит имя и тип колонки.
func (*ColumnType) DatabaseTypeName
func (ci *ColumnType) DatabaseTypeName() string
DatabaseTypeName возвращает системное имя базы данных типа столбца. Если возвращается пустая строка, значит, имя типа драйвером не поддерживается. Список типов данных драйвера см. в документации к драйверу.
Спецификаторы ColumnType.Length не учитываются. Общие имена типов включают “VARCHAR”, “TEXT”, “NVARCHAR”, “DECIMAL”, “BOOL”, “INT” и “BIGINT”.
func (*ColumnType) DecimalSize
func (ci *ColumnType) DecimalSize() (precision, scale int64, ok bool)
DecimalSize возвращает масштаб и точность десятичного типа. Если они не применяются или не поддерживаются, ok равно false.
func (*ColumnType) Length
func (ci *ColumnType) Length() (length int64, ok bool)
Length возвращает длину типа столбца для типов столбцов переменной длины, таких как текстовые и бинарные типы полей. Если длина типа не ограничена, то значение будет math.MaxInt64 (все ограничения базы данных будут действовать). Если тип столбца не переменной длины, например int, или если он не поддерживается драйвером, ok будет false.
func (*ColumnType) Name
func (ci *ColumnType) Name() string
Name возвращает имя или псевдоним колонки.
func (*ColumnType) Nullable
func (ci *ColumnType) Nullable() (nullable, ok bool)
Nullable сообщает, может ли столбец быть нулевым. Если драйвер не поддерживает это свойство, ok будет равно false.
func (*ColumnType) ScanType
func (ci *ColumnType) ScanType() reflect.Type
ScanType возвращает тип Go, подходящий для сканирования с помощью Rows.Scan. Если драйвер не поддерживает это свойство, ScanType вернет тип пустого интерфейса.
package main
import (
"context"
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// Подключение к базе данных
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/testdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Проверка соединения
if err := db.Ping(); err != nil {
log.Fatal(err)
}
// Выполнение запроса
rows, err := db.QueryContext(context.Background(),
"SELECT id, username, created_at, balance, is_active FROM users LIMIT 1")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// Получение информации о столбцах
columnTypes, err := rows.ColumnTypes()
if err != nil {
log.Fatal(err)
}
// Анализ метаданных каждого столбца
for i, ct := range columnTypes {
fmt.Printf("\n--- Столбец %d: %s ---\n", i+1, ct.Name())
// Тип данных в БД
databaseType := ct.DatabaseTypeName()
fmt.Printf("Тип в БД: %s\n", databaseType)
// Nullable
nullable, ok := ct.Nullable()
if ok {
fmt.Printf("Может быть NULL: %v\n", nullable)
} else {
fmt.Println("Информация о NULL недоступна")
}
// Длина/размер
length, ok := ct.Length()
if ok {
fmt.Printf("Длина: %d\n", length)
}
// Точность и масштаб (для чисел)
precision, scale, ok := ct.DecimalSize()
if ok {
fmt.Printf("Точность: %d, Масштаб: %d\n", precision, scale)
}
// Тип сканирования в Go
scanType := ct.ScanType()
fmt.Printf("Тип в Go: %v\n", scanType)
}
// Пример использования информации о типах для динамического сканирования
var (
id int64
username string
createdAt time.Time
balance float64
isActive bool
)
if rows.Next() {
err = rows.Scan(&id, &username, &createdAt, &balance, &isActive)
if err != nil {
log.Fatal(err)
}
fmt.Printf("\nРеальные значения: %d, %s, %v, %.2f, %t\n",
id, username, createdAt, balance, isActive)
}
}
6.6 - Описание типа Conn database/sql
type Conn struct {
// содержит отфильтрованные или неэкспонированные поля
}
Conn представляет собой одно соединение с базой данных, а не пул соединений с базой данных. Предпочтительнее запускать запросы из БД, если нет особой необходимости в постоянном соединении с одной базой данных.
Conn должен вызвать Conn.Close, чтобы вернуть соединение в пул баз данных, и может делать это одновременно с выполняющимся запросом.
После вызова Conn.Close все операции над соединением завершаются с ошибкой ErrConnDone.
func (*Conn) BeginTx
func (c *Conn) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error)
BeginTx начинает транзакцию.
Предоставленный контекст используется до тех пор, пока транзакция не будет зафиксирована или откачена. Если контекст будет отменен, пакет sql откатит транзакцию. Tx.Commit вернет ошибку, если контекст, предоставленный BeginTx, будет отменен.
Предоставленный параметр TxOptions является необязательным и может быть равен nil, если следует использовать значения по умолчанию. Если используется уровень изоляции не по умолчанию, который драйвер не поддерживает, будет возвращена ошибка.
func (*Conn) Close
func (c *Conn) Close() error
Close возвращает соединение в пул соединений. Все операции после Close возвращаются с ошибкой ErrConnDone. Close безопасно вызывать одновременно с другими операциями, и он будет блокироваться до тех пор, пока все остальные операции не завершатся. Может оказаться полезным сначала отменить любой используемый контекст, а затем вызвать Close непосредственно после него.
func (*Conn) ExecContext
func (c *Conn) ExecContext(ctx context.Context, query string, args ...any) (Result, error)
ExecContext выполняет запрос, не возвращая никаких строк. В качестве args используются любые параметры-заполнители в запросе.
Пример
package main
import (
"context"
"database/sql"
"log"
"os"
_ "github.com/go-sql-driver/mysql" // Импорт драйвера БД
)
func main() {
// 1. Инициализация подключения к БД
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
defer db.Close()
// 2. Проверка соединения
ctx := context.Background()
if err := db.PingContext(ctx); err != nil {
log.Fatal("Failed to ping database:", err)
}
// 3. Получение выделенного соединения
conn, err := db.Conn(ctx)
if err != nil {
log.Fatal("Failed to get connection:", err)
}
defer func() {
if err := conn.Close(); err != nil {
log.Printf("Warning: error closing connection: %v", err)
}
}()
// 4. Выполнение транзакции
id := 41
tx, err := conn.BeginTx(ctx, nil)
if err != nil {
log.Fatal("Failed to begin transaction:", err)
}
// 5. Выполнение UPDATE в транзакции
result, err := tx.ExecContext(ctx,
`UPDATE balances SET balance = balance + 10 WHERE user_id = ?`,
id,
)
if err != nil {
tx.Rollback()
log.Fatal("Failed to update balance:", err)
}
// 6. Проверка количества измененных строк
rows, err := result.RowsAffected()
if err != nil {
tx.Rollback()
log.Fatal("Failed to get rows affected:", err)
}
if rows != 1 {
tx.Rollback()
log.Printf("Expected single row affected, got %d rows affected", rows)
os.Exit(1) // Более мягкое завершение чем log.Fatal
}
// 7. Фиксация транзакции
if err := tx.Commit(); err != nil {
log.Fatal("Failed to commit transaction:", err)
}
log.Println("Balance updated successfully")
}
func (*Conn) PingContext
func (c *Conn) PingContext(ctx context.Context) error
PingContext проверяет, что соединение с базой данных все еще живо.
func (*Conn) PrepareContext
func (c *Conn) PrepareContext(ctx context.Context, query string) (*Stmt, error)
PrepareContext создает подготовленный запрос для последующих запросов или выполнений. Несколько запросов или выполнений могут быть запущены одновременно из полученного утверждения. Вызывающая сторона должна вызвать метод *Stmt.Close оператора, когда он больше не нужен.
Предоставленный контекст используется для подготовки утверждения, а не для его выполнения.
func (*Conn) QueryContext
func (c *Conn) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error)
QueryContext выполняет запрос, возвращающий строки, обычно SELECT. В args указываются любые параметры-заполнители запроса.
func (*Conn) QueryRowContext
func (c *Conn) QueryRowContext(ctx context.Context, query string, args ...any) *Row
QueryRowContext выполняет запрос, который, как ожидается, вернет не более одной строки. QueryRowContext всегда возвращает значение non-nil. Ошибки откладываются до вызова метода *Row.Scan. Если запрос не выбрал ни одной строки, то *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.
func (*Conn) Raw
func (c *Conn) Raw(f func(driverConn any) error) (err error)
Raw выполняет f, раскрывая базовое соединение драйвера на время выполнения f. DriverConn не должен использоваться вне f.
После того как f вернется и err не будет driver.ErrBadConn, Conn будет продолжать использоваться до вызова Conn.Close.
Разбираем метод Raw() для работы с низкоуровневыми соединениями
Метод Raw() в database/sql позволяет получить прямое доступ к драйвер-специфичному соединению, минуя абстракции пакета database/sql. Это нужно для использования специфичных функций драйвера, которые не доступны через стандартный API.
Что такое f в этом контексте?
f - это ваша функция-колбэк, которая получает доступ к нативному соединению драйвера. Она должна иметь сигнатуру:
func(driverConn any) error
Зачем это нужно?
- Использование специфичных функций драйвера (например, PostgreSQL-специфичные команды)
- Оптимизация производительности для критичных участков кода
- Работа с нестандартными возможностями БД
Простой пример (PostgreSQL)
package main
import (
"context"
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq"
)
func main() {
db, err := sql.Open("postgres", "user=postgres dbname=test sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close()
conn, err := db.Conn(context.Background())
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// Используем Raw для доступа к нативному соединению PostgreSQL
err = conn.Raw(func(driverConn interface{}) error {
// Приводим к типу, который ожидаем (для PostgreSQL)
pgConn, ok := driverConn.(driver.Conn) // Здесь нужен ваш тип соединения
if !ok {
return fmt.Errorf("unexpected driver connection type")
}
// Пример: выполняем LISTEN для PostgreSQL-нотификаций
// Это специфичная функция PostgreSQL, недоступная через стандартный API
_, err := pgConn.(*pq.Conn).Exec("LISTEN channel_name")
return err
})
if err != nil {
log.Fatal(err)
}
}
Более практичный пример (MySQL)
package main
import (
"context"
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
conn, err := db.Conn(context.Background())
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// Получаем низкоуровневое соединение MySQL
err = conn.Raw(func(driverConn interface{}) error {
// Приводим к ожидаемому типу соединения MySQL
mysqlConn, ok := driverConn.(*mysql.Conn) // Требуется import драйвера
if !ok {
return fmt.Errorf("expected mysql.Conn, got %T", driverConn)
}
// Используем специфичные методы MySQL
fmt.Println("Server version:", mysqlConn.GetServerVersion())
fmt.Println("Connection ID:", mysqlConn.GetConnectionID())
// Можно выполнить специфичные команды
return mysqlConn.Ping()
})
if err != nil {
log.Fatal(err)
}
}
Важные предупреждения:
Тип соединения зависит от драйвера:
- PostgreSQL:
*pq.Conn - MySQL:
*mysql.Conn - SQLite: зависит от используемого драйвера
- PostgreSQL:
Безопасность:
- Не сохраняйте соединение вне функции
f - Все ошибки должны быть обработаны
- Не сохраняйте соединение вне функции
Совместимость:
- Код становится зависимым от конкретного драйвера
- Может сломаться при смене драйвера
Когда стоит использовать Raw()?
- Когда вам нужен доступ к специфичным функциям СУБД
- Для оптимизации критических участков
- При работе с нестандартными возможностями (нотификации, специфичные команды)
В большинстве случаев стандартного API database/sql достаточно, и Raw() не понадобится.
6.7 - Описание типа DBStats database/sql
type DBStats
type DBStats struct {
MaxOpenConnections int // Максимальное количество открытых соединений с базой данных.
// Состояние пула
OpenConnections int // Количество установленных соединений, как используемых, так и простаивающих.
InUse int // Количество соединений, используемых в данный момент.
Idle int // Количество простаивающих соединений.
// Счетчики
WaitCount int64 // Общее количество ожидающих соединений.
WaitDuration time.Duration // Общее время, заблокированное в ожидании нового соединения.
MaxIdleClosed int64 // Общее количество соединений, закрытых из-за SetMaxIdleConns.
MaxIdleTimeClosed int64 // Общее количество соединений, закрытых из-за SetConnMaxIdleTime.
MaxLifetimeClosed int64 // Общее количество соединений, закрытых из-за SetConnMaxLifetime.
}
DBStats содержит статистику базы данных.
type IsolationLevel
type IsolationLevel int
IsolationLevel - это уровень изоляции транзакции, используемый в TxOptions.
const (
LevelDefault IsolationLevel = iota
LevelReadUncommitted
LevelReadCommitted
LevelWriteCommitted
LevelRepeatableRead
LevelSnapshot
LevelSerializable
LevelLinearizable
)
Различные уровни изоляции, которые драйверы могут поддерживать в DB.BeginTx. Если драйвер не поддерживает заданный уровень изоляции, может быть возвращена ошибка.
См. https://en.wikipedia.org/wiki/Isolation_(database_systems)#Isolation_levels.
func (IsolationLevel) String
func (i IsolationLevel) String() string
String возвращает имя уровня изоляции транзакции.
type NamedArg
type NamedArg struct {
// Name - имя параметра-заместителя.
//
// Если пусто, то будет использоваться
// порядковая позиция в списке аргументов.
//
// Имя не должно содержать префикса символа.
Name string
// Value - это значение параметра.
// Ему могут быть присвоены те же типы значений, что и аргументам запроса
//.
Value any
// содержит отфильтрованные или неэкспонированные поля
}
NamedArg - это именованный аргумент. Значения NamedArg могут использоваться в качестве аргументов DB.Query или DB.Exec и связываться с соответствующим именованным параметром в операторе SQL.
Для более краткого способа создания значений NamedArg см. функцию Named.
func Named
func Named(name string, value any) NamedArg
Named предоставляет более лаконичный способ создания значений NamedArg.
Пример
db.ExecContext(ctx, `
delete from Invoice
where
TimeCreated < @end
and TimeCreated >= @start;`,
sql.Named("start", startTime),
sql.Named("end", endTime),
)
type Null
type Null[T any] struct {
V T
Valid bool
}
Null представляет значение, которое может быть нулевым. Null реализует интерфейс Scanner, поэтому его можно использовать в качестве места сканирования:
var s Null[string]
err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&s)
...
if s.Valid {
// используем s.V
} else {
// NULL значение
}
T должен быть одним из типов, принимаемых driver.Value.
func (*Null[T]) Scan
func (n *Null[T]) Scan(value any) error
func (Null[T]) Value
func (n Null[T]) Value() (driver.Value, error)
type NullBool ¶
type NullBool struct {
Bool bool
Valid bool // Valid is true if Bool is not NULL
}
NullBool представляет bool, который может быть нулевым. NullBool реализует интерфейс Scanner, поэтому его можно использовать в качестве места сканирования, аналогично NullString.
func (*NullBool) Scan
func (n *NullBool) Scan(value any) error
Scan реализует интерфейс Scanner.
func (NullBool) Value
func (n NullBool) Value() (driver.Value, error)
Value реализует интерфейс driver.Valuer.
type NullByte
type NullByte struct {
Byte byte
Valid bool // Valid is true if Byte is not NULL
}
NullByte представляет байт, который может быть нулевым. NullByte реализует интерфейс Scanner, поэтому его можно использовать в качестве места сканирования, аналогично NullString.
func (*NullByte) Scan
func (n *NullByte) Scan(value any) error
Scan реализует интерфейс Scanner.
func (NullByte) Value
func (n NullByte) Value() (driver.Value, error)
Value реализует интерфейс driver.Valuer.
type NullFloat64 ¶
type NullFloat64 struct {
Float64 float64
Valid bool // Valid is true if Float64 is not NULL
}
NullFloat64 представляет float64, который может быть нулевым. NullFloat64 реализует интерфейс Scanner, поэтому его можно использовать в качестве места сканирования, аналогично NullString.
func (*NullFloat64) Scan ¶
func (n *NullFloat64) Scan(value any) error
Scan реализует интерфейс Scanner.
func (NullFloat64) Value ¶
func (n NullFloat64) Value() (driver.Value, error)
Value реализует интерфейс driver.Valuer.
type NullInt16
type NullInt16 struct {
Int16 int16
Valid bool // Valid is true if Int16 is not NULL
}
NullInt16 представляет int16, который может быть нулевым. NullInt16 реализует интерфейс Scanner, поэтому его можно использовать в качестве места сканирования, аналогично NullString.
func (*NullInt16) Scan
func (n *NullInt16) Scan(value any) error
Scan реализует интерфейс Scanner.
func (NullInt16) Value
func (n NullInt16) Value() (driver.Value, error)
Value реализует интерфейс driver.Valuer.
type NullInt32
type NullInt32 struct {
Int32 int32
Valid bool // Valid is true if Int32 is not NULL
}
NullInt32 представляет int32, который может быть нулевым. NullInt32 реализует интерфейс Scanner, поэтому его можно использовать в качестве места сканирования, аналогично NullString.
func (*NullInt32) Scan
func (n *NullInt32) Scan(value any) error
Scan реализует интерфейс Scanner.
func (NullInt32) Value
func (n NullInt32) Value() (driver.Value, error)
Value реализует интерфейс driver.Valuer.
type NullInt64
type NullInt64 struct {
Int64 int64
Valid bool // Valid is true if Int64 is not NULL
}
NullInt64 представляет int64, который может быть нулевым. NullInt64 реализует интерфейс Scanner, поэтому его можно использовать в качестве места сканирования, аналогично NullString.
func (*NullInt64) Scan
func (n *NullInt64) Scan(value any) error
Scan реализует интерфейс Scanner.
func (NullInt64) Value
func (n NullInt64) Value() (driver.Value, error)
Value реализует интерфейс driver.Valuer.
type NullString
type NullString struct {
String string
Valid bool // Valid is true if String is not NULL
}
NullString представляет строку, которая может быть нулевой. NullString реализует интерфейс Scanner, поэтому его можно использовать в качестве места сканирования:
var s NullString
err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&s)
...
if s.Valid {
// использовать s.String
} else {
// NULL значение
}
func (*NullString) Scan
func (ns *NullString) Scan(value any) error
Scan реализует интерфейс Scanner.
func (NullString) Value
func (ns NullString) Value() (driver.Value, error)
Value реализует интерфейс driver.Valuer.
type NullTime
type NullTime struct {
Time.Time
Valid bool // Valid is true if Time is not NULL
}
NullTime представляет время time.Time, которое может быть нулевым. NullTime реализует интерфейс Scanner, поэтому его можно использовать в качестве места сканирования, аналогично NullString.
func (*NullTime) Scan
func (n *NullTime) Scan(value any) error
Scan реализует интерфейс Scanner.
func (NullTime) Value
func (n NullTime) Value() (driver.Value, error)
Value реализует интерфейс driver.Valuer.
Практическое использование Null-типов в Go
Null-типы (NullString, NullInt64, NullTime и др.) нужны для работы с NULL-значениями из базы данных. Разберём на реальных примерах.
Интерфейсы
- Scanner - позволяет сканировать (читать) значение из БД
- driver.Valuer - позволяет преобразовывать значение для записи в БД
Полный пример с NullString и NullTime
Пример
package main
import (
"database/sql"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
type UserProfile struct {
ID int64
Name sql.NullString
Bio sql.NullString
DeletedAt sql.NullTime
}
func main() {
db, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 1. Запись данных с NULL-значениями
profile := UserProfile{
Name: sql.NullString{String: "Иван Иванов", Valid: true},
Bio: sql.NullString{}, // NULL
DeletedAt: sql.NullTime{Time: time.Now(), Valid: false}, // Будет записан как NULL
}
_, err = db.Exec(
"INSERT INTO users (name, bio, deleted_at) VALUES (?, ?, ?)",
profile.Name,
profile.Bio,
profile.DeletedAt,
)
if err != nil {
log.Fatal(err)
}
// 2. Чтение данных с возможными NULL-значениями
var user UserProfile
err = db.QueryRow(`
SELECT id, name, bio, deleted_at
FROM users
WHERE id = ?
`, 1).Scan(
&user.ID,
&user.Name,
&user.Bio,
&user.DeletedAt,
)
if err != nil {
log.Fatal(err)
}
// 3. Обработка NULL-значений
log.Println("ID:", user.ID)
if user.Name.Valid {
log.Println("Name:", user.Name.String)
} else {
log.Println("Name not set")
}
if user.Bio.Valid {
log.Println("Bio:", user.Bio.String)
} else {
log.Println("Bio not set")
}
if user.DeletedAt.Valid {
log.Println("Deleted at:", user.DeletedAt.Time)
} else {
log.Println("Not deleted")
}
}
Как работают Null-типы?
1. Scan (чтение из БД)
Пример
func (ns *NullString) Scan(value interface{}) error {
if value == nil {
ns.String, ns.Valid = "", false
return nil
}
ns.Valid = true
return convertAssign(&ns.String, value)
}
2. Value (запись в БД)
Пример
func (ns NullString) Value() (driver.Value, error) {
if !ns.Valid {
return nil, nil
}
return ns.String, nil
}
Пример с пользовательским Null-типом
Создадим свой NullStatus для enum-поля:
Пример
type NullStatus struct {
Status string
Valid bool
}
func (ns *NullStatus) Scan(value interface{}) error {
if value == nil {
ns.Status, ns.Valid = "", false
return nil
}
ns.Valid = true
switch v := value.(type) {
case []byte:
ns.Status = string(v)
case string:
ns.Status = v
default:
return fmt.Errorf("unsupported type: %T", value)
}
return nil
}
func (ns NullStatus) Value() (driver.Value, error) {
if !ns.Valid {
return nil, nil
}
return ns.Status, nil
}
Когда использовать Null-типы?
- Когда поле в БД может быть NULL
- Когда нужно отличать “нулевое значение” от “неустановленного”
- При работе с опциональными полями
Альтернативы
В новых версиях Go можно использовать указатели:
Пример
var name *string
err := db.QueryRow("SELECT name FROM users WHERE id = 1").Scan(&name)
if name != nil {
// есть значение
}
Но Null-типы предоставляют более удобный интерфейс и явный флаг Valid.
6.8 - Описание типа Out database/sql
type Out
type Out struct {
// Dest — указатель на значение, которое будет установлено в качестве результата
// параметра OUTPUT хранящейся процедуры.
Dest any
// In — является ли параметр параметром INOUT. Если да, то входное значение для хранящейся
// процедуры — это разыменованное значение указателя Dest, которое затем заменяется
// выходным значением.
In bool
// содержит отфильтрованные или неэкспортированные поля
}
Out может использоваться для извлечения параметров OUTPUT из хранимых процедур.
Не все драйверы и базы данных поддерживают параметры OUTPUT.
Пример использования:
var outArg string
_, err := db.ExecContext(ctx, «ProcName», sql.Named(«Arg1», sql.Out{Dest: &outArg}))
type RawBytes
type RawBytes []byte
RawBytes — это байтовый срез, который содержит ссылку на память, принадлежащую самой базе данных. После выполнения Rows.Scan в RawBytes срез остается действительным только до следующего вызова Rows.Next, Rows.Scan или Rows.Close.
RawBytes - это специальный тип в пакете database/sql, определённый как []byte, который используется для сканирования данных из базы данных без копирования памяти. Это может быть полезно для оптимизации производительности при работе с большими бинарными данными.
Основные принципы работы с RawBytes
- Временное владение памятью: RawBytes содержит ссылку на память, управляемую драйвером БД, а не на копию данных.
- Ограниченное время жизни: Данные в RawBytes действительны только до следующего вызова методов:
Next()Scan()Close()
- Небезопасность: Если сохранить RawBytes и попытаться использовать после вышеуказанных вызовов, это приведёт к неопределённому поведению.
Пример 1: Базовое сканирование
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {
panic(err)
}
defer db.Close()
rows, err := db.Query("SELECT blob_column FROM my_table WHERE id = ?", 1)
if err != nil {
panic(err)
}
defer rows.Close()
for rows.Next() {
var raw sql.RawBytes
if err := rows.Scan(&raw); err != nil {
panic(err)
}
// Используем raw сразу, пока он действителен
fmt.Printf("Data: %s\n", raw)
// Если нужно сохранить данные, нужно сделать копию:
dataCopy := make([]byte, len(raw))
copy(dataCopy, raw)
// Теперь dataCopy можно использовать после rows.Next()
}
}
Пример 2: Сканирование нескольких столбцов
func scanMultipleColumns(db *sql.DB) error {
rows, err := db.Query("SELECT id, name, data FROM documents")
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var id int
var name string
var data sql.RawBytes
if err := rows.Scan(&id, &name, &data); err != nil {
return err
}
fmt.Printf("ID: %d, Name: %s, Data length: %d\n", id, name, len(data))
// Обработка данных должна быть здесь
processData(data)
}
return rows.Err()
}
func processData(data []byte) {
// Обработка данных
}
Пример 3: Опасное использование (неправильное)
func incorrectUsage(db *sql.DB) {
rows, err := db.Query("SELECT data FROM large_blobs")
if err != nil {
panic(err)
}
defer rows.Close()
var allData [][]byte
for rows.Next() {
var raw sql.RawBytes
if err := rows.Scan(&raw); err != nil {
panic(err)
}
// ОШИБКА: сохраняем RawBytes, который станет недействительным
allData = append(allData, raw)
}
// Здесь allData содержит недействительные данные!
for _, data := range allData {
fmt.Println(data) // Может привести к панике или неверным данным
}
}
Когда использовать RawBytes
- Для больших бинарных данных, когда копирование нежелательно
- Для временной обработки данных, которые не нужно сохранять
- Когда производительность критична, и вы готовы следить за временем жизни данных
Альтернативы
Если нужно сохранить данные:
var raw sql.RawBytes
var data []byte
rows.Scan(&raw)
data = make([]byte, len(raw))
copy(data, raw)
// Теперь data можно использовать в любом месте
type Result ¶
type Result интерфейс {
// LastInsertId возвращает целое число, сгенерированное базой данных
// в ответ на команду. Обычно это будет из
// столбца «автоинкремент» при вставке новой строки. Не все
// базы данных поддерживают эту функцию, и синтаксис таких
// операторов варьируется.
LastInsertId() (int64, error)
// RowsAffected возвращает количество строк, затронутых
// обновлением, вставкой или удалением. Не все базы данных или драйверы баз данных
// драйвер базы данных может поддерживать эту функцию.
RowsAffected() (int64, error)
}
Результат обобщает выполненную команду SQL.
6.9 - Описание типа Row database/sql
type Row
type Row struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Row — результат вызова DB.QueryRow для выбора одной строки.
func (*Row) Err
func (r *Row) Err() error
Err предоставляет способ для обертывающих пакетов проверять ошибки запроса без вызова Row.Scan. Err возвращает ошибку, если таковая имела место при выполнении запроса. Если эта ошибка не равна nil, она также будет возвращена из Row.Scan.
func (*Row) Scan
func (r *Row) Scan(dest ...any) error
Scan копирует столбцы из соответствующей строки в значения, на которые указывает dest. Подробности см. в документации по Rows.Scan. Если запросу соответствует более одной строки, Scan использует первую строку и отбрасывает остальные. Если ни одна строка не соответствует запросу, Scan возвращает ErrNoRows.
type Rows
type Rows struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Rows — это результат запроса. Его курсор начинается перед первой строкой набора результатов. Используйте Rows.Next для перехода от строки к строке.
Пример
package main
import (
"context"
"database/sql"
"log"
"strings"
)
// Глобальные переменные лучше инициализировать
var (
ctx = context.Background() // Инициализация контекста
db *sql.DB
)
func main() {
// На практике db должен быть инициализирован перед использованием
// Например:
// var err error
// db, err = sql.Open("driver-name", "datasource")
// if err != nil {
// log.Fatal(err)
// }
// defer db.Close()
age := 27
// Добавляем проверку, что db не nil
if db == nil {
log.Fatal("database connection is not initialized")
}
// Используем QueryContext вместо Query для явного указания контекста
rows, err := db.QueryContext(ctx, "SELECT name FROM users WHERE age=?", age)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
names := make([]string, 0)
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
log.Fatal(err)
}
names = append(names, name)
}
// Проверяем ошибки после итерации
if err := rows.Err(); err != nil {
log.Fatal(err)
}
// Добавляем проверку на пустой результат
if len(names) == 0 {
log.Printf("No users found with age %d", age)
return
}
log.Printf("%s are %d years old", strings.Join(names, ", "), age)
}
func (*Rows) Close
func (rs *Rows) Close() error
Close закрывает Rows, предотвращая дальнейшее перечисление. Если Rows.Next вызывается и возвращает false, а дальнейших наборов результатов нет, Rows закрывается автоматически, и достаточно проверить результат Rows.Err. Close является идемпотентным и не влияет на результат Rows.Err.
func (*Rows) ColumnTypes
func (rs *Rows) ColumnTypes() ([]*ColumnType, error)
ColumnTypes возвращает информацию о столбцах, такую как тип столбца, длина и возможность принятия нулевого значения. Некоторая информация может быть недоступна из некоторых драйверов.
func (*Rows) Columns
func (rs *Rows) Columns() ([]string, error)
Columns возвращает имена столбцов. Columns возвращает ошибку, если строки закрыты.
func (*Rows) Err
func (rs *Rows) Err() error
Err возвращает ошибку, если таковая возникла во время итерации. Err может быть вызван после явного или неявного Rows.Close.
func (*Rows) Next
func (rs *Rows) Next() bool
Next подготавливает следующую строку результата для чтения с помощью метода Rows.Scan. Он возвращает true в случае успеха или false, если следующей строки результата нет или при ее подготовке произошла ошибка. Для различения этих двух случаев следует обратиться к Rows.Err.
Каждому вызову Rows.Scan, даже первому, должен предшествовать вызов Rows.Next.
Функция Rows.Next() используется для итерации по строкам результата SQL-запроса.
Пример
Базовый пример использования Rows.Next()
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// Подключение к базе данных
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Выполнение запроса
rows, err := db.Query("SELECT id, name, email FROM users WHERE active = ?", true)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// Итерация по строкам результата
for rows.Next() {
var id int
var name, email string
// Сканирование значений из текущей строки
if err := rows.Scan(&id, &name, &email); err != nil {
log.Fatal(err)
}
fmt.Printf("ID: %d, Name: %s, Email: %s\n", id, name, email)
}
// Проверка ошибок после итерации
if err := rows.Err(); err != nil {
log.Fatal(err)
}
}
Почему используется цикл for?
Цикл for rows.Next() выполняет итерации по всем строкам результата запроса:
Количество итераций равно количеству строк, возвращенных запросом.
Механизм работы:
- При первом вызове
rows.Next()перемещает курсор к первой строке результата - При последующих вызовах перемещает курсор к следующей строке
- Когда строки заканчиваются, возвращает
falseи цикл завершается
- При первом вызове
Важно: Каждый вызов
rows.Scan()должен быть предварен вызовомrows.Next()
Детализированный пример с обработкой ошибок
func getActiveUsers(db *sql.DB) ([]User, error) {
rows, err := db.Query("SELECT id, name, email FROM users WHERE active = ?", true)
if err != nil {
return nil, fmt.Errorf("query failed: %v", err)
}
defer rows.Close()
var users []User
for rows.Next() {
var u User
if err := rows.Scan(&u.ID, &u.Name, &u.Email); err != nil {
// Можно продолжить обработку других строк или прервать
return nil, fmt.Errorf("scan failed: %v", err)
}
users = append(users, u)
}
// Проверяем ошибки, которые могли возникнуть во время итерации
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("rows iteration failed: %v", err)
}
return users, nil
}
type User struct {
ID int
Name string
Email string
}
Что происходит внутри цикла?
Первая итерация:
rows.Next()перемещает курсор к первой строке (если она есть)- Возвращает
true, если строка доступна - Выполняется
rows.Scan()
Последующие итерации:
rows.Next()перемещает курсор к следующей строке- Цикл продолжается, пока есть строки
Завершение:
- Когда строки заканчиваются,
rows.Next()возвращаетfalse - Цикл прерывается
- Проверяется
rows.Err()на наличие ошибок
- Когда строки заканчиваются,
Важные нюансы:
- Всегда вызывайте
defer rows.Close()для освобождения ресурсов - Проверяйте
rows.Err()после цикла для выявления ошибок итерации - Не используйте
rows.Next()без последующегоrows.Scan() - Для пустых результатов цикл не выполнится ни разу
Альтернативный пример с обработкой пустого результата
func printUserCount(db *sql.DB) {
rows, err := db.Query("SELECT COUNT(*) FROM users")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// rows.Next() вернет true, даже если только одна строка
if rows.Next() {
var count int
if err := rows.Scan(&count); err != nil {
log.Fatal(err)
}
fmt.Printf("Total users: %d\n", count)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
}
Этот пример показывает, что даже для запросов, возвращающих одну строку (как COUNT), необходимо использовать rows.Next() перед rows.Scan().
func (*Rows) NextResultSet
func (rs *Rows) NextResultSet() bool
NextResultSet подготавливает следующий набор результатов для чтения. Он сообщает, есть ли дальнейшие наборы результатов, или false, если дальнейших наборов результатов нет или если произошла ошибка при переходе к ним. Для различения этих двух случаев следует обратиться к методу Rows.Err.
После вызова NextResultSet перед сканированием всегда следует вызывать метод Rows.Next. Если есть дальнейшие наборы результатов, они могут не содержать строк в наборе результатов.
Функция NextResultSet() используется, когда запрос к базе данных возвращает несколько наборов результатов (например, при выполнении хранимых процедур или пакетных запросов).
Пример
Пример с несколькими наборами результатов
package main
import (
"context"
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql" // Импорт драйвера MySQL
)
func main() {
// Инициализация подключения к базе данных
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Запрос, возвращающий несколько наборов результатов
// (например, хранимая процедура с несколькими SELECT)
query := `
SELECT id, name FROM users WHERE active = 1;
SELECT id, title FROM products WHERE price > 100;
SELECT COUNT(*) FROM orders WHERE status = 'completed';
`
// Выполнение запроса
rows, err := db.QueryContext(context.Background(), query)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// Обработка первого набора результатов (пользователи)
fmt.Println("=== Active Users ===")
for rows.Next() {
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
log.Fatal(err)
}
fmt.Printf("ID: %d, Name: %s\n", id, name)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
// Переход к следующему набору результатов (продукты)
if rows.NextResultSet() {
fmt.Println("\n=== Expensive Products ===")
for rows.Next() {
var id int
var title string
if err := rows.Scan(&id, &title); err != nil {
log.Fatal(err)
}
fmt.Printf("ID: %d, Title: %s\n", id, title)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
}
// Переход к следующему набору результатов (количество заказов)
if rows.NextResultSet() {
fmt.Println("\n=== Completed Orders Count ===")
for rows.Next() {
var count int
if err := rows.Scan(&count); err != nil {
log.Fatal(err)
}
fmt.Printf("Completed orders: %d\n", count)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
}
// Проверяем, есть ли еще наборы результатов
if rows.NextResultSet() {
fmt.Println("\nThere are more result sets available")
} else {
fmt.Println("\nNo more result sets")
}
}
Ключевые моменты работы с NextResultSet():
Порядок обработки: Всегда сначала обрабатывайте текущий набор результатов с помощью
Next()иScan(), прежде чем переходить к следующему.Проверка наличия результатов:
NextResultSet()возвращаетtrue, если есть следующий набор результатов, даже если он пустой.Обработка ошибок: После
NextResultSet()всегда проверяйтеrows.Err()на наличие ошибок.Пустые наборы: Набор результатов может не содержать строк - это нормально, просто
Next()вернетfalseсразу.
Альтернативный пример с хранимой процедурой
func callMultiResultStoredProcedure(db *sql.DB) error {
// Вызов хранимой процедуры, возвращающей несколько наборов результатов
rows, err := db.Query("CALL get_report_data()")
if err != nil {
return err
}
defer rows.Close()
// Обработка всех наборов результатов
resultSetIndex := 0
for {
// Обработка текущего набора результатов
for rows.Next() {
// В зависимости от набора результатов используем разную логику сканирования
switch resultSetIndex {
case 0:
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
return err
}
fmt.Printf("User: %d - %s\n", id, name)
case 1:
var product string
var price float64
if err := rows.Scan(&product, &price); err != nil {
return err
}
fmt.Printf("Product: %s - $%.2f\n", product, price)
// Можно добавить дополнительные case для других наборов
}
}
if err := rows.Err(); err != nil {
return err
}
// Переход к следующему набору результатов
if !rows.NextResultSet() {
break
}
resultSetIndex++
}
return nil
}
Важные предупреждения:
- Не все драйверы баз данных поддерживают несколько наборов результатов.
- Всегда проверяйте
rows.Err()послеNextResultSet(). - После перехода к новому набору результатов обязательно вызывайте
rows.Next()перед сканированием. - Структура данных в каждом наборе результатов может отличаться.
func (*Rows) Scan
func (rs *Rows) Scan(dest ...any) error
Scan копирует столбцы в текущей строке в значения, на которые указывает dest. Количество значений в dest должно совпадать с количеством столбцов в Rows.
Scan преобразует столбцы, прочитанные из базы данных, в следующие общие типы Go и специальные типы, предоставляемые пакетом sql:
*string
*[]byte
*int, *int8, *int16, *int32, *int64
*uint, *uint8, *uint16, *uint32, *uint64
*bool
*float32, *float64
*interface{}
*RawBytes
*Rows (cursor value)
любой тип, реализующий Scanner (см. документацию по Scanner)
В самом простом случае, если тип значения из исходного столбца является целым, bool или строковым типом T, а dest имеет тип *T, Scan просто присваивает значение через указатель.
Scan также преобразует строковые и числовые типы, если при этом не теряется информация. В то время как Scan преобразует все числа, сканированные из числовых столбцов базы данных, в *string, сканирование в числовые типы проверяется на переполнение. Например, float64 со значением 300 или строка со значением «300» могут быть отсканированы в uint16, но не в uint8, хотя float64(255) или «255» могут быть отсканированы в uint8. Исключением является то, что при сканировании некоторых чисел float64 в строки может произойти потеря информации при преобразовании в строку. В общем случае, сканируйте столбцы с плавающей запятой в *float64.
Если аргумент dest имеет тип *[]byte, Scan сохраняет в этом аргументе копию соответствующих данных. Копия принадлежит вызывающему и может быть изменена и храниться неограниченное время. Копирование можно избежать, используя вместо этого аргумент типа *RawBytes; ограничения на его использование см. в документации по RawBytes.
Если аргумент имеет тип *interface{}, Scan копирует значение, предоставленное базовым драйвером, без преобразования. При сканировании из исходного значения типа []byte в *interface{} создается копия среза, и вызывающая сторона владеет результатом.
Исходные значения типа time.Time могут быть отсканированы в значения типа *time.Time, *interface{}, *string или *[]byte. При преобразовании в два последних используется time.RFC3339Nano.
Источниковые значения типа bool могут быть просканированы в типы *bool, *interface{}, *string, *[]byte или *RawBytes.
Для сканирования в *bool источником могут быть true, false, 1, 0 или строковые входы, которые можно проанализировать с помощью strconv.ParseBool.
Scan также может преобразовать курсор, возвращенный из запроса, такого как «select cursor(select * from my_table) from dual», в значение *Rows, которое само по себе может быть просканировано. Родительский запрос select закроет любой курсор *Rows, если родительский *Rows закрыт.
Если любой из первых аргументов, реализующих Scanner, возвращает ошибку, эта ошибка будет обернута в возвращаемую ошибку.
type Scanner
type Scanner interface {
// Scan назначает значение из драйвера базы данных.
//
// Значение src будет одного из следующих типов:
//
// int64
// float64
// bool
// []byte
// string
// time.Time
// nil - для значений NULL
//
// Если значение не может быть сохранено
// без потери информации.
//
// Типы ссылок, такие как []byte, действительны только до следующего вызова Scan
// и не должны сохраняться. Их базовая память принадлежит драйверу.
// Если сохранение необходимо, скопируйте их значения перед следующим вызовом Scan.
Scan(src any) error
}
Scanner — интерфейс, используемый Rows.Scan.
6.10 - Описание типа Stmt database/sql
type Stmt
type Stmt struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Stmt — это подготовленное выражение. Stmt безопасно для одновременного использования несколькими goroutines.
Если Stmt подготовлен на Tx или Conn, он будет навсегда привязан к одному базовому соединению. Если Tx или Conn закрывается, Stmt станет непригодным для использования, и все операции будут возвращать ошибку. Если Stmt подготовлен на DB, он будет оставаться пригодным для использования в течение всего срока жизни DB. Когда Stmt необходимо выполнить на новом базовом соединении, он автоматически подготовится на новом соединении.
Пример
package main
import (
"context"
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql" // Добавляем импорт драйвера
)
func main() {
// Инициализируем контекст
ctx := context.Background()
// Инициализируем соединение с базой данных (в реальном коде)
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Проверяем соединение
if err := db.PingContext(ctx); err != nil {
log.Fatal(err)
}
// Создаем подготовленное выражение
stmt, err := db.PrepareContext(ctx, "SELECT username FROM users WHERE id = ?")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
// Выполняем запрос с параметром
id := 43
var username string
err = stmt.QueryRowContext(ctx, id).Scan(&username)
switch {
case err == sql.ErrNoRows:
log.Printf("no user with id %d", id) // Используем Printf вместо Fatalf чтобы не завершать программу
case err != nil:
log.Fatal(err)
default:
log.Printf("username is %s\n", username)
}
}
func (*Stmt) Close
func (s *Stmt) Close() error
Close закрывает оператор.
func (*Stmt) Exec
func (s *Stmt) Exec(args ...any) (Result, error)
Exec выполняет подготовленный оператор с заданными аргументами и возвращает Result, обобщающий результат оператора.
Exec использует context.Background внутренне; чтобы указать контекст, используйте Stmt.ExecContext.
func (*Stmt) ExecContext
func (s *Stmt) ExecContext(ctx context.Context, args ...any) (Result, error)
ExecContext выполняет подготовленное выражение с заданными аргументами и возвращает Result, обобщающий результат выражения.
func (*Stmt) Query
func (s *Stmt) Query(args ...any) (*Rows, error)
Query выполняет подготовленный запрос с заданными аргументами и возвращает результаты запроса в виде *Rows.
Query использует context.Background внутренне; для указания контекста используйте Stmt.QueryContext.
func (*Stmt) QueryContext ¶
func (s *Stmt) QueryContext(ctx context.Context, args ...any) (*Rows, error)
QueryContext выполняет подготовленное запросное выражение с заданными аргументами и возвращает результаты запроса в виде *Rows.
func (*Stmt) QueryRow
func (s *Stmt) QueryRow(args ...any) *Row
QueryRow выполняет подготовленный запрос с заданными аргументами. Если во время выполнения запроса происходит ошибка, она будет возвращена вызовом Scan на возвращенном *Row, который всегда не равен nil. Если запрос не выбирает никаких строк, *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.
Пример использования:
var name string
err := nameByUseridStmt.QueryRow(id).Scan(&name)
QueryRow использует context.Background внутренне; для указания контекста используйте Stmt.QueryRowContext.
func (*Stmt) QueryRowContext
func (s *Stmt) QueryRowContext(ctx context.Context, args ...any) *Row
QueryRowContext выполняет подготовленный запрос с заданными аргументами. Если во время выполнения запроса происходит ошибка, она будет возвращена вызовом Scan на возвращенном *Row, который всегда не равен nil. Если запрос не выбирает никаких строк, *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.
Пример
package main
import (
"context"
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql" // Не забудьте импортировать драйвер
)
func main() {
// 1. Инициализация контекста
ctx := context.Background()
// 2. Инициализация подключения к БД (пример для MySQL)
db, err := sql.Open("mysql", "username:password@tcp(localhost:3306)/dbname")
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
defer db.Close()
// 3. Проверка соединения
if err := db.PingContext(ctx); err != nil {
log.Fatal("Failed to ping database:", err)
}
// 4. Создание подготовленного выражения
stmt, err := db.PrepareContext(ctx, "SELECT username FROM users WHERE id = ?")
if err != nil {
log.Fatal("Failed to prepare statement:", err)
}
defer stmt.Close()
// 5. Выполнение запроса
id := 43
var username string
err = stmt.QueryRowContext(ctx, id).Scan(&username)
// 6. Обработка результатов
switch {
case err == sql.ErrNoRows:
log.Printf("User with id %d not found", id) // Не фатальная ошибка
case err != nil:
log.Fatal("Query failed:", err)
default:
log.Printf("Found user: %s (ID: %d)", username, id)
}
}
6.11 - Описание типа Tx database/sql
type Tx
type Tx struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Tx — это незавершенная транзакция базы данных.
Транзакция должна заканчиваться вызовом Tx.Commit или Tx.Rollback.
После вызова Tx.Commit или Tx.Rollback все операции по транзакции завершаются с ошибкой ErrTxDone.
Инструкции, подготовленные для транзакции вызовом методов Tx.Prepare или Tx.Stmt транзакции, закрываются вызовом Tx.Commit или Tx.Rollback.
func (*Tx) Commit
func (tx *Tx) Commit() error
Commit фиксирует транзакцию.
func (*Tx) Exec
func (tx *Tx) Exec(query string, args ...any) (Result, error)
Exec выполняет запрос, который не возвращает строки. Например: INSERT и UPDATE.
Exec использует context.Background внутренне; для указания контекста используйте Tx.ExecContext.
func (*Tx) ExecContext
func (tx *Tx) ExecContext(ctx context.Context, query string, args ...any) (Result, error)
ExecContext выполняет запрос, который не возвращает строки. Например: INSERT и UPDATE.
Пример
package main
import (
"context"
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql" // Импорт драйвера БД
)
func main() {
// Инициализация подключения к БД
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
defer db.Close()
// Проверка соединения
ctx := context.Background()
if err := db.PingContext(ctx); err != nil {
log.Fatal("Database connection failed:", err)
}
// Начало транзакции
tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
if err != nil {
log.Fatal("Failed to begin transaction:", err)
}
// Выполнение операций в транзакции
id := 37
_, execErr := tx.ExecContext(ctx, "UPDATE users SET status = ? WHERE id = ?", "paid", id)
if execErr != nil {
// Попытка отката при ошибке
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Printf("UPDATE failed: %v, rollback also failed: %v", execErr, rollbackErr)
} else {
log.Printf("UPDATE failed, transaction rolled back: %v", execErr)
}
return
}
// Фиксация транзакции
if err := tx.Commit(); err != nil {
log.Fatal("Failed to commit transaction:", err)
}
log.Println("Transaction completed successfully")
}
func (*Tx) Prepare
func (tx *Tx) Prepare(query string) (*Stmt, error)
Prepare создает подготовленное выражение для использования в транзакции.
Возвращаемое выражение работает в рамках транзакции и будет закрыто после фиксации или отката транзакции.
Чтобы использовать существующее подготовленное выражение в этой транзакции, см. Tx.Stmt.
Prepare использует context.Background внутренне; чтобы указать контекст, используйте Tx.PrepareContext.
Пример
package main
import (
"context"
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql" // Импорт драйвера БД
)
func main() {
// Инициализация подключения к БД
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname?parseTime=true")
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
defer db.Close()
// Проверка соединения
if err := db.Ping(); err != nil {
log.Fatal("Database connection failed:", err)
}
projects := []struct {
mascot string
release int
}{
{"tux", 1991},
{"duke", 1996},
{"gopher", 2009},
{"moby dock", 2013},
}
// Начало транзакции
ctx := context.Background()
tx, err := db.BeginTx(ctx, nil)
if err != nil {
log.Fatal("Failed to begin transaction:", err)
}
// Откат в случае ошибки (игнорируется если был Commit)
defer func() {
if err := tx.Rollback(); err != nil && err != sql.ErrTxDone {
log.Printf("Warning: rollback failed: %v", err)
}
}()
// Подготовка выражения
stmt, err := tx.PrepareContext(ctx, "INSERT INTO projects(id, mascot, release, category) VALUES(?, ?, ?, ?)")
if err != nil {
log.Fatal("Failed to prepare statement:", err)
}
defer stmt.Close()
// Вставка данных
for id, project := range projects {
if _, err := stmt.ExecContext(ctx, id+1, project.mascot, project.release, "open source"); err != nil {
log.Fatal("Failed to insert project:", err)
}
}
// Фиксация транзакции
if err := tx.Commit(); err != nil {
log.Fatal("Failed to commit transaction:", err)
}
log.Println("Successfully inserted", len(projects), "projects")
}
Дополнительные рекомендации:
Параметры подключения:
db.SetMaxOpenConns(25) db.SetMaxIdleConns(25) db.SetConnMaxLifetime(5*time.Minute)Пакетная вставка: Для большого количества данных рассмотрите возможность:
// Начало пакетной вставки values := make([]interface{}, 0, len(projects)*4) query := "INSERT INTO projects(id, mascot, release, category) VALUES" for id, project := range projects { if id > 0 { query += "," } query += "(?, ?, ?, ?)" values = append(values, id+1, project.mascot, project.release, "open source") } if _, err := tx.ExecContext(ctx, query, values...); err != nil { log.Fatal(err) }Таймауты:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel()Валидация данных: Добавьте проверку данных перед вставкой:
if project.release < 1990 || project.release > time.Now().Year() { log.Printf("Invalid release year for %s: %d", project.mascot, project.release) continue }
func (*Tx) PrepareContext
func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error)
PrepareContext создает подготовленное выражение для использования в транзакции.
Возвращенное выражение работает в рамках транзакции и будет закрыто после фиксации или отката транзакции.
Чтобы использовать существующее подготовленное выражение в этой транзакции, см. Tx.Stmt.
Предоставленный контекст будет использоваться для подготовки контекста, а не для выполнения возвращаемого оператора. Возвращаемый оператор будет выполняться в контексте транзакции.
func (*Tx) Query
func (tx *Tx) Query(query string, args ...any) (*Rows, error)
Query выполняет запрос, который возвращает строки, обычно SELECT.
Query использует context.Background внутренне; чтобы указать контекст, используйте Tx.QueryContext.
func (*Tx) QueryContext
func (tx *Tx) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error)
QueryContext выполняет запрос, который возвращает строки, обычно SELECT.
func (*Tx) QueryRow
func (tx *Tx) QueryRow(query string, args ...any) *Row
QueryRow выполняет запрос, который должен вернуть не более одной строки. QueryRow всегда возвращает значение, отличное от nil. Ошибки откладываются до вызова метода Scan Row. Если запрос не выбирает ни одной строки, *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.
QueryRow использует context.Background внутренне; для указания контекста используйте Tx.QueryRowContext.
func (*Tx) QueryRowContext
func (tx *Tx) QueryRowContext(ctx context.Context, query string, args ...any) *Row
QueryRowContext выполняет запрос, который должен вернуть не более одной строки. QueryRowContext всегда возвращает значение, отличное от nil. Ошибки откладываются до вызова метода Scan Row. Если запрос не выбирает ни одной строки, *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.
func (*Tx) Rollback
func (tx *Tx) Rollback() error
Rollback прерывает транзакцию.
Пример
код транзакции для обновления водителей и заказов
package main
import (
"context"
"database/sql"
"log"
_ "github.com/lib/pq" // Импорт драйвера PostgreSQL
)
func main() {
// Инициализация подключения к БД
connStr := "user=postgres dbname=mydb sslmode=disable"
db, err := sql.Open("postgres", connStr)
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
defer db.Close()
// Проверка соединения
if err := db.Ping(); err != nil {
log.Fatal("Database connection failed:", err)
}
// Создаем контекст с таймаутом
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Начало транзакции
tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
if err != nil {
log.Fatal("Failed to begin transaction:", err)
}
// Гарантированный откат при ошибках
defer func() {
if err := tx.Rollback(); err != nil && err != sql.ErrTxDone {
log.Printf("Warning: rollback error: %v", err)
}
}()
id := 53
// 1. Обновление статуса водителя
_, err = tx.ExecContext(ctx, "UPDATE drivers SET status = $1 WHERE id = $2", "assigned", id)
if err != nil {
log.Printf("Failed to update driver status: %v", err)
return
}
// 2. Назначение водителя на заказы
_, err = tx.ExecContext(ctx, "UPDATE pickups SET driver_id = $1 WHERE driver_id IS NULL", id)
if err != nil {
log.Printf("Failed to assign driver to pickups: %v", err)
return
}
// Фиксация транзакции
if err := tx.Commit(); err != nil {
log.Fatal("Failed to commit transaction:", err)
}
log.Printf("Successfully assigned driver %d and updated pickup orders", id)
}
Дополнительные улучшения:
Безопасность UPDATE:
// Ограничиваем обновление только незанятыми заказами "UPDATE pickups SET driver_id = $1 WHERE driver_id IS NULL"Проверка результата:
res, err := tx.ExecContext(...) if rowsAffected, _ := res.RowsAffected(); rowsAffected == 0 { log.Printf("Warning: no rows were updated") }Повторные попытки:
maxRetries := 3 for i := 0; i < maxRetries; i++ { // ... выполнение транзакции ... if err == nil { break } if shouldRetry(err) { continue } break }Connection Pool:
db.SetMaxOpenConns(25) db.SetMaxIdleConns(25) db.SetConnMaxLifetime(5*time.Minute)
func (*Tx) Stmt
func (tx *Tx) Stmt(stmt *Stmt) *Stmt
Stmt возвращает подготовленное заявление, специфичное для транзакции, из существующего заявления.
Пример:
updateMoney, err := db.Prepare(«UPDATE balance SET money=money+? WHERE id=?»)
...
tx, err := db.Begin()...
res, err := tx.Stmt(updateMoney).Exec(123.45, 98293203)
Возвращенное выражение работает в рамках транзакции и будет закрыто после фиксации или отката транзакции.
Stmt использует context.Background внутри всегда; для указания контекста используйте Tx.StmtContext.
func (*Tx) StmtContext
func (tx *Tx) StmtContext(ctx context.Context, stmt *Stmt) *Stmt
StmtContext возвращает подготовленное выражение, специфичное для транзакции, из существующего выражения.
Пример:
updateMoney, err := db.Prepare(«UPDATE balance SET money=money+? WHERE id=?») … tx, err := db.Begin()…
res, err := tx.StmtContext(ctx, updateMoney).Exec(123.45, 98293203) Предоставленный контекст используется для подготовки оператора, а не для его выполнения.
Возвращенный оператор работает в рамках транзакции и будет закрыт после фиксации или отката транзакции.
type TxOptions
type TxOptions struct {
// Isolation — уровень изоляции транзакции.
// Если равен нулю, используется уровень по умолчанию драйвера или базы данных.
Isolation IsolationLevel
ReadOnly bool
}
TxOptions содержит параметры транзакции, которые будут использоваться в DB.BeginTx.
7 - Описание пакета flag языка программирования Go
Использование
Определите флаги с помощью flag.String, Bool, Int и т. д.
Это объявляет целочисленный флаг -n, хранящийся в указателе nFlag, с типом *int:
import «flag»
var nFlag = flag.Int("n", 1234, "сообщение справки для флага n")
При желании можно привязать флаг к переменной с помощью функций Var().
var flagvar int
func init() {
flag.IntVar(&flagvar, "flagname", 1234, "сообщение справки для flagname")
}
Или можно создать пользовательские флаги, которые удовлетворяют интерфейсу Value (с указателями-приемниками), и связать их с разбором флагов с помощью
flag.Var(&flagVal, "name", "сообщение справки для flagname")
Для таких флагов значением по умолчанию является просто начальное значение переменной.
После определения всех флагов вызовите
flag.Parse()
чтобы проанализировать командную строку в определенные флаги.
Затем флаги можно использовать напрямую. Если вы используете сами флаги, все они являются указателями; если вы связываете их с переменными, они являются значениями.
fmt.Println(«ip имеет значение », *ip)
fmt.Println(«flagvar имеет значение », flagvar)
После разбора аргументы, следующие за флагами, доступны как срез flag.Args или по отдельности как flag.Arg(i). Аргументы индексируются от 0 до flag.NArg-1.
Синтаксис флагов командной строки ¶
Допускаются следующие формы:
-flag
--flag // также допускаются двойные тире
-flag=x
-flag x // только небулевые флаги
Можно использовать один или два тире; они эквивалентны. Последняя форма не допускается для булевых флагов, поскольку значение команды
cmd -x *
где * — подстановочный знак оболочки Unix, изменится, если есть файл с именем 0, false и т. д. Для отключения булевого флага необходимо использовать форму -flag=false.
Разбор флагов прекращается непосредственно перед первым аргументом, не являющимся флагом («-» является аргументом, не являющимся флагом), или после терминатора «–».
- Целочисленные флаги принимают значения
1234,0664,0x1234и могут быть отрицательными. - Булевы флаги могут быть:
1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False
Флаги продолжительности принимают любой ввод, допустимый для time.ParseDuration.
Набор флагов командной строки по умолчанию управляется функциями верхнего уровня. Тип FlagSet позволяет определять независимые наборы флагов, например для реализации подкоманд в интерфейсе командной строки. Методы FlagSet аналогичны функциям верхнего уровня для набора флагов командной строки.
Пример
package main
import (
"errors"
"flag"
"fmt"
"os"
"strings"
"time"
)
// Пример 1: Флаг строки с именем "species" и значением по умолчанию "gopher"
var species = flag.String("species", "gopher", "the species we are studying")
// Пример 2: Два флага (длинный и короткий) с общей переменной
var gopherType string
func init() {
const (
defaultGopher = "pocket"
usage = "the variety of gopher"
)
flag.StringVar(&gopherType, "gopher_type", defaultGopher, usage)
flag.StringVar(&gopherType, "g", defaultGopher, usage+" (shorthand)")
}
// Пример 3: Пользовательский тип флага - срез интервалов времени
type interval []time.Duration
// String реализует интерфейс flag.Value
func (i *interval) String() string {
if len(*i) == 0 {
return ""
}
var b strings.Builder
for idx, d := range *i {
if idx > 0 {
b.WriteString(", ")
}
b.WriteString(d.String())
}
return b.String()
}
// Set реализует интерфейс flag.Value
func (i *interval) Set(value string) error {
// Разрешаем множественные установки флага для накопления значений
for _, dt := range strings.Split(value, ",") {
duration, err := time.ParseDuration(strings.TrimSpace(dt))
if err != nil {
return fmt.Errorf("invalid duration %q: %v", dt, err)
}
*i = append(*i, duration)
}
return nil
}
var intervalFlag interval
func init() {
flag.Var(&intervalFlag, "deltaT", "comma-separated list of intervals to use between events")
}
func main() {
// Парсим флаги
flag.Parse()
// Выводим значения флагов
fmt.Println("Species:", *species)
fmt.Println("Gopher type:", gopherType)
fmt.Println("Intervals:", intervalFlag.String())
// Пример использования
if len(intervalFlag) == 0 {
fmt.Println("No intervals specified. Use -deltaT flag")
os.Exit(1)
}
// Демонстрация работы с интервалами
for i, d := range intervalFlag {
fmt.Printf("Event %d will happen after %v\n", i+1, d)
time.Sleep(d) // В реальном коде здесь была бы полезная работа
}
}
Как использовать:
- Сборка:
go build -o app
- Примеры запуска:
# Простой запуск
./app -species=rabbit -gopher_type=arctic -deltaT=1s,2s,3s
# Использование короткого флага
./app -g=arctic -deltaT=500ms,1s
# Просмотр помощи
./app -help
Переменные
var ErrHelp = errors.New("flag: help requested")
ErrHelp - это ошибка, возвращаемая при вызове флага -help или -h, но такой флаг не определен.
var Usage = func() {
fmt.Fprintf(CommandLine.Output(), "Usage of %s:\n", os.Args[0])
PrintDefaults()
}
Usage печатает сообщение об использовании всех определенных флагов командной строки в выходной файл CommandLine, который по умолчанию является os.Stderr.
Функция вызывается при возникновении ошибки во время разбора флагов.
Функция - это переменная, которая может быть изменена для указания на пользовательскую функцию. По умолчанию она печатает простой заголовок и вызывает PrintDefaults; подробности о формате вывода и о том, как им управлять, см. в документации к PrintDefaults.
Пользовательские функции могут выбрать выход из программы; по умолчанию выход происходит в любом случае, поскольку стратегия обработки ошибок в командной строке установлена на ExitOnError.
7.1 - Функции пакета flag
func Arg
func Arg(i int) string
Arg возвращает i-й аргумент командной строки. Arg(0) — это первый аргумент, оставшийся после обработки флагов. Arg возвращает пустую строку, если запрошенный элемент не существует.
func Args
func Args() []string
Args возвращает аргументы командной строки, не являющиеся флагами.
func Bool
func Bool(name string, value bool, usage string) *bool
Bool определяет флаг bool с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной bool, в которой хранится значение флага.
func BoolFunc
добавлено в go1.21.0
func BoolFunc(name, usage string, fn func(string) error)
BoolFunc определяет флаг с указанным именем и строкой использования без требования значений. Каждый раз, когда флаг встречается, вызывается fn со значением флага. Если fn возвращает ошибку, отличную от nil, она будет рассматриваться как ошибка разбора значения флага.
Пример
package main
import (
"flag"
"fmt"
"os"
)
func main() {
// Создаем новый набор флагов
fs := flag.NewFlagSet("ExampleBoolFunc", flag.ContinueOnError)
fs.SetOutput(os.Stdout) // Выводим ошибки в stdout
// Регистрируем BoolFunc флаг
fs.BoolFunc("log", "logs a dummy message (default: false)", func(s string) error {
if s == "" {
// Без значения - просто флаг присутствует
fmt.Println("dummy message: flag is set")
return nil
}
// С значением - выводим его
fmt.Printf("dummy message: %s\n", s)
return nil
})
// Пример 1: Флаг без значения
fmt.Println("\nCase 1: -log (no value)")
if err := fs.Parse([]string{"-log"}); err != nil {
fmt.Println("Error:", err)
}
// Пример 2: Флаг с произвольным значением
fmt.Println("\nCase 2: -log=0")
if err := fs.Parse([]string{"-log=0"}); err != nil {
fmt.Println("Error:", err)
}
// Пример 3: Неизвестный флаг
fmt.Println("\nCase 3: -unknown")
if err := fs.Parse([]string{"-unknown"}); err != nil {
fmt.Println("Error:", err)
}
// Пример 4: Вывод помощи
fmt.Println("\nCase 4: -help")
fs.PrintDefaults()
}
Запуск программы:
go run main.go -log -log=hello
Результат:
Case 1: -log (no value)
dummy message: flag is set
Case 2: -log=0
dummy message: 0
Case 3: -unknown
Error: flag provided but not defined: -unknown
Case 4: -help
-log
logs a dummy message (default: false)
func BoolVar
func BoolVar(p *bool, name string, value bool, usage string)
BoolVar определяет флаг bool с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную bool, в которой будет храниться значение флага.
func Duration
func Duration(name string, value time.Duration, usage string) *time.Duration
Duration определяет флаг time.Duration с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной time.Duration, в которой хранится значение флага. Флаг принимает значение, допустимое для time.ParseDuration.
func DurationVar
func DurationVar(p *time.Duration, name string, value time.Duration, usage string)
DurationVar определяет флаг time.Duration с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную time.Duration, в которой хранится значение флага. Флаг принимает значение, допустимое для time.ParseDuration.
func Float64
func Float64(name string, value float64, usage string) *float64
Float64 определяет флаг float64 с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной float64, в которой хранится значение флага.
func Float64Var
func Float64Var(p *float64, name string, value float64, usage string)
Float64Var определяет флаг float64 с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную float64, в которой хранится значение флага.
func Float64Var
func Float64Var(p *float64, name string, value float64, usage string)
Float64Var определяет флаг float64 с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную float64, в которой будет храниться значение флага.
func Func
func Func(name, usage string, fn func(string) error)
Func определяет флаг с указанным именем и строкой использования. Каждый раз, когда флаг встречается, вызывается функция fn со значением флага. Если fn возвращает ошибку, отличную от nil, это будет расценено как ошибка разбора значения флага.
Пример
package main
import (
"errors"
"flag"
"fmt"
"net"
"os"
)
func main() {
// Создаем новый набор флагов
fs := flag.NewFlagSet("ExampleFunc", flag.ContinueOnError)
fs.SetOutput(os.Stdout)
// Переменная для хранения IP
var ip net.IP
// Регистрируем функцию-парсер для IP
fs.Func("ip", "`IP address` to parse (e.g. 192.168.1.1 or ::1)", func(s string) error {
parsedIP := net.ParseIP(s)
if parsedIP == nil {
return fmt.Errorf("invalid IP address format: %q", s)
}
ip = parsedIP
return nil
})
// Тест 1: Валидный IPv4 адрес
fmt.Println("Test case 1: Valid IPv4 address")
if err := fs.Parse([]string{"-ip", "127.0.0.1"}); err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Parsed IP: %v\n", ip)
fmt.Printf("Is loopback: %t\n\n", ip.IsLoopback())
}
// Тест 2: Невалидный IP адрес
fmt.Println("Test case 2: Invalid IP address")
if err := fs.Parse([]string{"-ip", "256.0.0.1"}); err != nil {
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Printf("Parsed IP: %v\n", ip)
}
// Тест 3: Валидный IPv6 адрес
fmt.Println("Test case 3: Valid IPv6 address")
if err := fs.Parse([]string{"-ip", "::1"}); err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Parsed IP: %v\n", ip)
fmt.Printf("Is loopback: %t\n\n", ip.IsLoopback())
}
// Вывод справки
fmt.Println("Help message:")
fs.PrintDefaults()
}
Вывод программы:
Test case 1: Valid IPv4 address
Parsed IP: 127.0.0.1
Is loopback: true
Test case 2: Invalid IP address
Error: invalid IP address format: "256.0.0.1"
Test case 3: Valid IPv6 address
Parsed IP: ::1
Is loopback: true
Help message:
-ip IP address
`IP address` to parse (e.g. 192.168.1.1 or ::1)
func Int
func Int(name string, value int, usage string) *int
Int определяет флаг int с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной int, в которой хранится значение флага.
func Int64
func Int64(name string, value int64, usage string) *int64
Int64 определяет флаг int64 с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной int64, в которой хранится значение флага.
func Int64Var
func Int64Var(p *int64, name string, value int64, usage string)
Int64Var определяет флаг int64 с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную int64, в которой хранится значение флага.
func IntVar
func IntVar(p *int, name string, value int, usage string)
IntVar определяет флаг int с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную int, в которой будет храниться значение флага.
func NArg
func NArg() int
NArg — количество аргументов, оставшихся после обработки флагов.
func NFlag
func NFlag() int
NFlag возвращает количество установленных флагов командной строки.
func Parse
func Parse()
Parse анализирует флаги командной строки из os.Args[1:]. Должен вызываться после определения всех флагов и до доступа к флагам из программы.
func Parsed
func Parsed() bool
Parsed сообщает, были ли проанализированы флаги командной строки.
func PrintDefaults
func PrintDefaults()
PrintDefaults выводит в стандартный поток ошибок (если не настроено иначе) сообщение об использовании, показывающее настройки по умолчанию всех определенных флагов командной строки. Для флага x с целочисленным значением вывод по умолчанию имеет вид
-x int
usage-message-for-x (default 7)
Сообщение об использовании будет отображаться в отдельной строке для всех флагов, кроме флагов типа bool с однобайтовым именем.
Для флагов типа bool тип опускается, и если имя флага состоит из одного байта, сообщение об использовании отображается в той же строке.
Значение по умолчанию в скобках опускается, если значение по умолчанию для типа равно нулю.
Указанный тип, в данном случае int, можно изменить, поместив имя в обратные кавычки в строке использования флага; первый такой элемент в сообщении принимается за имя параметра, которое будет отображаться в сообщении, а обратные кавычки удаляются из сообщения при отображении. Например, при задании
flag.String("I", "", "search `directory` for include files")
вывод будет следующим
-I directory
search directory for include files.
Чтобы изменить место назначения для сообщений флага, вызовите CommandLine.SetOutput.
func Set
func Set(name, value string) error
Set устанавливает значение флага командной строки с указанным именем.
func String
func String(name string, value string, usage string) *string
String определяет строковый флаг с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес строковой переменной, в которой хранится значение флага.
func StringVar
func StringVar(p *string, name string, value string, usage string)
StringVar определяет строковый флаг с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на строковую переменную, в которой хранится значение флага.
func TextVar
func TextVar(p encoding.TextUnmarshaler, name string, value encoding.TextMarshaler, usage string)
TextVar определяет флаг с указанным именем, значением по умолчанию и строкой использования. Аргумент p должен быть указателем на переменную, которая будет хранить значение флага, и p должен реализовывать encoding.TextUnmarshaler. Если флаг используется, его значение будет передано методу UnmarshalText p. Тип значения по умолчанию должен быть таким же, как тип p.
Пример
package main
import (
"flag"
"fmt"
"net"
"os"
)
func main() {
// Создаем новый набор флагов
fs := flag.NewFlagSet("ExampleTextVar", flag.ContinueOnError)
fs.SetOutput(os.Stdout)
// Переменная для хранения IP с значением по умолчанию
var ip net.IP = net.IPv4(192, 168, 0, 100)
// Регистрируем TextVar флаг для парсинга IP
fs.TextVar(&ip, "ip", ip, "`IP address` to parse (e.g. 192.168.1.1 or ::1)")
// Тест 1: Валидный IPv4 адрес
fmt.Println("Test case 1: Valid IPv4 address (127.0.0.1)")
if err := fs.Parse([]string{"-ip", "127.0.0.1"}); err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Parsed IP: %v\n", ip)
fmt.Printf("Is loopback: %t\n\n", ip.IsLoopback())
}
// Тест 2: Невалидный IP адрес
fmt.Println("Test case 2: Invalid IP address (256.0.0.1)")
ip = nil // Сбрасываем предыдущее значение
if err := fs.Parse([]string{"-ip", "256.0.0.1"}); err != nil {
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Printf("Parsed IP: %v\n\n", ip)
}
// Тест 3: Вывод справки
fmt.Println("Test case 3: Help message")
fs.PrintDefaults()
}
Вывод программы:
Test case 1: Valid IPv4 address (127.0.0.1)
Parsed IP: 127.0.0.1
Is loopback: true
Test case 2: Invalid IP address (256.0.0.1)
Error: invalid IP address: 256.0.0.1
Test case 3: Help message
-ip IP address
`IP address` to parse (e.g. 192.168.1.1 or ::1) (default 192.168.0.100)
func Uint
func Uint(name string, value uint, usage string) *uint
Uint определяет флаг uint с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной uint, которая хранит значение флага.
func Uint64
func Uint64(name string, value uint64, usage string) *uint64
Uint64 определяет флаг uint64 с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной uint64, в которой хранится значение флага.
func Uint64Var
func Uint64Var(p *uint64, name string, value uint64, usage string)
Uint64Var определяет флаг uint64 с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную uint64, в которой будет храниться значение флага.
func UintVar
func UintVar(p *uint, name string, value uint, usage string)
UintVar определяет флаг uint с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную uint, в которой будет храниться значение флага.
func UnquoteUsage
func UnquoteUsage(flag *Flag) (name string, usage string)
UnquoteUsage извлекает имя в обратных кавычках из строки использования для флага и возвращает его и использование без кавычек. При заданном “a `name` to show” возвращает (“name”, “a name to show”). Если обратные кавычки отсутствуют, имя является обоснованным предположением о типе значения флага или пустой строкой, если флаг является булевым.
func Var
func Var(value Value, name string, usage string)
Var определяет флаг с указанным именем и строкой использования. Тип и значение флага представлены первым аргументом типа Value, который обычно содержит пользовательскую реализацию Value. Например, вызывающий может создать флаг, который преобразует строку, разделенную запятыми, в набор строк, предоставив набору методы Value; в частности, Set разложит строку, разделенную запятыми, на набор.
Пример
Пример использования flag.Var() с пользовательским типом
package main
import (
"errors"
"flag"
"fmt"
"strings"
)
// StringSet - пользовательский тип для множества строк
type StringSet map[string]struct{}
// String реализует интерфейс flag.Value
func (s StringSet) String() string {
return strings.Join(s.ToSlice(), ",")
}
// Set реализует интерфейс flag.Value
func (s StringSet) Set(value string) error {
if len(s) > 0 {
return errors.New("StringSet already set")
}
for _, item := range strings.Split(value, ",") {
item = strings.TrimSpace(item)
if item != "" {
s[item] = struct{}{}
}
}
return nil
}
// ToSlice преобразует StringSet в срез строк
func (s StringSet) ToSlice() []string {
result := make([]string, 0, len(s))
for k := range s {
result = append(result, k)
}
return result
}
func main() {
// Инициализируем наш StringSet
var tags StringSet = make(map[string]struct{})
// Регистрируем флаг через flag.Var
flag.Var(&tags, "tags", "comma-separated list of tags")
// Парсим аргументы командной строки
flag.Parse()
// Выводим результат
fmt.Println("Tags count:", len(tags))
fmt.Println("Tags list:", tags.ToSlice())
// Примеры запуска:
// go run main.go -tags=go,programming,example
// go run main.go -tags="rust, systems programming"
}
Ключевые компоненты:
Пользовательский тип
StringSet:- Реализует интерфейс
flag.Valueс методамиString()иSet() - Хранит уникальные строки как ключи map
- Реализует интерфейс
Метод
Set():- Разбивает строку по запятым
- Игнорирует пустые элементы
- Возвращает ошибку при повторной установке
Метод
String():- Формирует строку из элементов через запятую
- Используется для вывода значения по умолчанию
Дополнительный метод
ToSlice():- Удобное преобразование в срез строк
Как это работает:
При вызове
flag.Var()пакет flag получает:- Указатель на наш StringSet
- Имя флага (“tags”)
- Описание для справки
При парсинге аргументов:
- Для флага
-tags=go,programmingвызываетсяSet("go,programming") - Наш метод разбивает строку и заполняет map
- Для флага
При выводе справки:
- Вызывается
String()для показа текущего значения
- Вызывается
func Visit
func Visit(fn func(*Flag))
Visit посещает флаги командной строки в лексикографическом порядке, вызывая fn для каждого из них. Он посещает только те флаги, которые были установлены.
func VisitAll
func VisitAll(fn func(*Flag))
VisitAll посещает флаги командной строки в лексикографическом порядке, вызывая fn для каждого из них. Он посещает все флаги, даже те, которые не были установлены.
7.2 - Типы пакета flag
type ErrorHandling
type ErrorHandling int
ErrorHandling определяет, как будет вести себя FlagSet.Parse в случае неудачи разбора.
const (
ContinueOnError ErrorHandling = iota // Возвращаем описательную ошибку.
ExitOnError // Вызов os.Exit(2) или для -h/-help Exit(0).
PanicOnError // Вызываем панику с описанием ошибки.
)
Эти константы заставляют FlagSet.Parse вести себя так, как описано в случае неудачи разбора.
type Flag
type Flag struct {
Name string // имя в командной строке
Usage string // справочное сообщение
Value Value // значение по умолчанию
DefValue string // значение по умолчанию (в виде текста); для сообщения об использовании
}
Флаг представляет состояние флага.
func Lookup
func Lookup(name string) *Flag
Lookup возвращает структуру Flag именованного флага командной строки, возвращая nil, если таковой не существует.
type FlagSet
type FlagSet struct {
// Usage - это функция, вызываемая при возникновении ошибки во время разбора флагов.
// Поле представляет собой функцию (не метод), которая может быть изменена для указания на
// пользовательский обработчик ошибок. То, что произойдет после вызова Usage, зависит
// от настройки ErrorHandling; для командной строки по умолчанию
// установлено значение ExitOnError, которое завершает работу программы после вызова Usage.
Usage func()
// содержит отфильтрованные или неэкспонированные поля
}
FlagSet представляет собой набор определенных флагов. Нулевое значение FlagSet не имеет имени и имеет обработку ошибки ContinueOnError.
Имена флагов должны быть уникальными в пределах FlagSet. Попытка определить флаг, имя которого уже используется, приведет к панике.
Пример
package main
import (
"flag"
"fmt"
"os"
"time"
)
func main() {
if len(os.Args) < 2 {
printUsage()
os.Exit(1)
}
switch os.Args[1] {
case "start":
start(os.Args[2:])
case "stop":
stop(os.Args[2:])
case "help", "-h", "--help":
printUsage()
default:
fmt.Printf("error: unknown command %q\n", os.Args[1])
printUsage()
os.Exit(1)
}
}
func start(args []string) {
fs := flag.NewFlagSet("start", flag.ExitOnError)
addr := fs.String("addr", ":8080", "address to listen on")
logLevel := fs.String("log-level", "info", "log level (debug, info, warn, error)")
if err := fs.Parse(args); err != nil {
fs.Usage()
os.Exit(1)
}
fmt.Printf("Starting server on %s with log level %s\n", *addr, *logLevel)
// Здесь была бы реальная логика запуска сервера
}
func stop(args []string) {
fs := flag.NewFlagSet("stop", flag.ExitOnError)
timeout := fs.Duration("timeout", 5*time.Second, "stop timeout duration")
force := fs.Bool("force", false, "force shutdown")
if err := fs.Parse(args); err != nil {
fs.Usage()
os.Exit(1)
}
fmt.Printf("Stopping server (timeout=%v, force=%t)\n", *timeout, *force)
// Здесь была бы реальная логика остановки сервера
}
func printUsage() {
fmt.Println("Usage:")
fmt.Println(" httpd start [-addr :port] [-log-level level]")
fmt.Println(" httpd stop [-timeout duration] [-force]")
fmt.Println(" httpd help")
fmt.Println("\nFlags:")
fmt.Println(" -h, --help Show this help message")
}
Как использовать:
- Запуск сервера:
./httpd start -addr :9999 -log-level debug
- Остановка сервера:
./httpd stop -timeout 10s -force
- Просмотр справки:
./httpd help
# или
./httpd -h
var CommandLine *FlagSet
CommandLine - это набор флагов командной строки по умолчанию, взятый из os.Args. Функции верхнего уровня, такие как BoolVar, Arg и так далее, являются обертками для методов CommandLine.
func NewFlagSet
func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet
NewFlagSet возвращает новый пустой набор флагов с указанным именем и свойством обработки ошибок. Если имя не пустое, оно будет выведено в сообщении об использовании по умолчанию и в сообщениях об ошибках.
Пример
Объяснение flag.NewFlagSet с примерами
Функция flag.NewFlagSet создает новый независимый набор флагов, что особенно полезно для реализации:
- Подкоманд в CLI-приложениях (например,
git commit,docker run) - Изолированных групп флагов
- Повторного использования флагов в разных контекстах
Параметры:
name- имя набора флагов (используется в сообщениях об ошибках и справке)errorHandling- стратегия обработки ошибок:flag.ContinueOnError- продолжить выполнение после ошибкиflag.ExitOnError- вызватьos.Exit(2)при ошибкеflag.PanicOnError- вызватьpanicпри ошибке
Пример 1: Базовое использование
package main
import (
"flag"
"fmt"
)
func main() {
// Создаем новый набор флагов
fs := flag.NewFlagSet("example", flag.ExitOnError)
// Добавляем флаги
verbose := fs.Bool("verbose", false, "enable verbose output")
port := fs.Int("port", 8080, "port to listen on")
// Парсим аргументы
fs.Parse([]string{"-verbose", "-port", "9090"})
// Используем значения
fmt.Printf("Verbose: %v, Port: %d\n", *verbose, *port)
}
Пример 2: Подкоманды (как в git/docker)
package main
import (
"flag"
"fmt"
"os"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("Expected 'greet' or 'time' subcommands")
os.Exit(1)
}
switch os.Args[1] {
case "greet":
// Набор флагов для команды greet
greetCmd := flag.NewFlagSet("greet", flag.ExitOnError)
name := greetCmd.String("name", "World", "name to greet")
greetCmd.Parse(os.Args[2:])
fmt.Printf("Hello, %s!\n", *name)
case "time":
// Набор флагов для команды time
timeCmd := flag.NewFlagSet("time", flag.ExitOnError)
format := timeCmd.String("format", "15:04:05", "time format")
timeCmd.Parse(os.Args[2:])
fmt.Println("Current time:", time.Now().Format(*format))
default:
fmt.Printf("Unknown command %q\n", os.Args[1])
os.Exit(1)
}
}
Пример 3: Разные стратегии обработки ошибок
package main
import (
"flag"
"fmt"
)
func main() {
// ContinueOnError - продолжить после ошибки
fs1 := flag.NewFlagSet("continue", flag.ContinueOnError)
fs1.Int("port", 0, "port number")
err := fs1.Parse([]string{"-port", "invalid"})
fmt.Println("ContinueOnError:", err)
// ExitOnError - выход при ошибке
fs2 := flag.NewFlagSet("exit", flag.ExitOnError)
fs2.Int("port", 0, "port number")
// В реальном коде это вызвало бы os.Exit(2)
fmt.Print("ExitOnError: ")
fs2.Parse([]string{"-port", "invalid"})
}
Рекомендации по использованию:
- Для CLI-приложений используйте
flag.ExitOnError - Для библиотек лучше
flag.ContinueOnError - Имя набора флагов помогает пользователю понять контекст ошибки
- Можно создавать несколько независимых наборов флагов в одной программе
func (*FlagSet) Arg
func (f *FlagSet) Arg(i int) string
Arg возвращает i-й аргумент. Arg(0) — это первый оставшийся аргумент после обработки флагов. Arg возвращает пустую строку, если запрошенный элемент не существует.
func (*FlagSet) Args
func (f *FlagSet) Args() []string
Args возвращает аргументы, не являющиеся флагами.
func (*FlagSet) Bool
func (f *FlagSet) Bool(name string, value bool, usage string) *bool
Bool определяет флаг bool с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной bool, в которой хранится значение флага.
func (*FlagSet) BoolFunc
func (f *FlagSet) BoolFunc(name, usage string, fn func(string) error)
BoolFunc определяет флаг с указанным именем и строкой использования без требования значений. Каждый раз, когда флаг встречается, вызывается fn со значением флага. Если fn возвращает ошибку, отличную от nil, она будет рассматриваться как ошибка разбора значения флага.
func (*FlagSet) BoolVar
func (f *FlagSet) BoolVar(p *bool, name string, value bool, usage string)
BoolVar определяет флаг bool с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную bool, в которой будет храниться значение флага.
func (*FlagSet) Duration
func (f *FlagSet) Duration(name string, value time.Duration, usage string) *time.Duration
Duration определяет флаг time.Duration с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной time.Duration, в которой хранится значение флага. Флаг принимает значение, допустимое для time.ParseDuration.
Пример
Объяснение flag.Duration с примерами
Метод Duration создает флаг для работы с временными интервалами (тип time.Duration). Это особенно полезно для параметров, которые представляют временные значения (таймауты, интервалы и т.д.).
Основные параметры:
name- имя флага (например, “timeout”)value- значение по умолчанию (например, 5*time.Second)usage- описание флага для справки
Пример 1: Простое использование
package main
import (
"flag"
"fmt"
"time"
)
func main() {
// Создаем флаг с продолжительностью
timeout := flag.Duration("timeout", 3*time.Second, "operation timeout duration")
// Парсим аргументы командной строки
flag.Parse()
// Используем значение
fmt.Printf("Waiting for %v before timeout...\n", *timeout)
time.Sleep(*timeout)
fmt.Println("Timeout reached!")
}
Использование:
go run main.go -timeout=2s
# или
go run main.go -timeout=500ms
Пример 2: В составе FlagSet (подкоманды)
package main
import (
"flag"
"fmt"
"time"
)
func main() {
serverCmd := flag.NewFlagSet("server", flag.ExitOnError)
// Флаги для команды server
port := serverCmd.Int("port", 8080, "port to listen on")
readTimeout := serverCmd.Duration("read-timeout", 5*time.Second, "read timeout")
writeTimeout := serverCmd.Duration("write-timeout", 10*time.Second, "write timeout")
serverCmd.Parse([]string{"-port=9000", "-read-timeout=2s", "-write-timeout=5s"})
fmt.Printf("Server config:\n")
fmt.Printf(" Port: %d\n", *port)
fmt.Printf(" Read Timeout: %v\n", *readTimeout)
fmt.Printf(" Write Timeout: %v\n", *writeTimeout)
}
Пример 3: Поддержка разных форматов времени
Флаг Duration автоматически поддерживает все форматы, которые понимает time.ParseDuration:
package main
import (
"flag"
"fmt"
)
func main() {
interval := flag.Duration("interval", 1*time.Minute, "check interval")
flag.Parse()
fmt.Printf("Checking every %v\n", *interval)
fmt.Printf("In nanoseconds: %d\n", interval.Nanoseconds())
}
Допустимые форматы:
- “300ms” - миллисекунды
- “1.5h” - часы с дробной частью
- “2h45m” - комбинированный формат
- “1d” - дни (расширение Go, означает 24h)
Типичные сценарии использования:
- Таймауты операций:
timeout := flag.Duration("timeout", 30*time.Second, "operation timeout")
- Интервалы повторения:
interval := flag.Duration("interval", 1*time.Hour, "check interval")
- Временные ограничения:
ttl := flag.Duration("ttl", 24*time.Hour, "time to live")
func (*FlagSet) DurationVar
func (f *FlagSet) DurationVar(p *time.Duration, name string, value time.Duration, usage string)
DurationVar определяет флаг time.Duration с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную time.Duration, в которой хранится значение флага. Флаг принимает значение, допустимое для time.ParseDuration.
func (*FlagSet) ErrorHandling
func (f *FlagSet) ErrorHandling() ErrorHandling
ErrorHandling возвращает поведение обработки ошибок набора флагов.
Пример
Объяснение ErrorHandling() с примерами
Метод ErrorHandling() возвращает текущую стратегию обработки ошибок для набора флагов (FlagSet). Это полезно, когда нужно проверить или изменить поведение обработки ошибок во время выполнения.
Возможные значения:
flag.ContinueOnError- продолжить выполнение после ошибкиflag.ExitOnError- вызватьos.Exit(2)при ошибкеflag.PanicOnError- вызватьpanicпри ошибке
Пример 1: Проверка текущей стратегии
package main
import (
"flag"
"fmt"
)
func main() {
// Создаем набор флагов с разными стратегиями
fs1 := flag.NewFlagSet("server", flag.ContinueOnError)
fs2 := flag.NewFlagSet("client", flag.ExitOnError)
// Проверяем стратегии обработки ошибок
fmt.Println("Server error handling:", fs1.ErrorHandling())
fmt.Println("Client error handling:", fs2.ErrorHandling())
}
Вывод:
Server error handling: 1 // ContinueOnError
Client error handling: 2 // ExitOnError
Пример 2: Динамическое изменение поведения
package main
import (
"flag"
"fmt"
"os"
)
func main() {
fs := flag.NewFlagSet("app", flag.ContinueOnError)
fs.Int("port", 8080, "port number")
// Для демонстрации - имитируем ошибку
args := []string{"-port", "invalid"}
// Парсим с текущей стратегией (ContinueOnError)
err := fs.Parse(args)
fmt.Printf("With ContinueOnError: error=%v, port=%d\n", err, fs.Lookup("port").Value)
// Меняем стратегию на ExitOnError
fs.Init(fs.Name(), flag.ExitOnError)
fmt.Println("New error handling:", fs.ErrorHandling())
// При новой стратегии парсинг вызовет os.Exit(2)
fmt.Print("With ExitOnError: ")
fs.Parse(args) // В реальности здесь будет выход
}
Пример 3: Использование в кастомной обработке ошибок
package main
import (
"flag"
"fmt"
"os"
)
func main() {
fs := flag.NewFlagSet("config", flag.ContinueOnError)
fs.String("config", "", "config file path")
fs.Duration("timeout", 5*time.Second, "operation timeout")
err := fs.Parse(os.Args[1:])
switch fs.ErrorHandling() {
case flag.ContinueOnError:
if err != nil {
fmt.Printf("Configuration error: %v\n", err)
// Продолжаем работу с значениями по умолчанию
}
case flag.ExitOnError:
// Обработка уже выполнена flag.Parse
case flag.PanicOnError:
// panic уже вызван
}
// Основная логика приложения
fmt.Println("Using config:", fs.Lookup("config").Value)
}
Основные сценарии использования:
- Библиотеки - когда нужно передать управление ошибкой вызывающему коду
- Тестирование - проверка поведения при разных стратегиях
- Гибкие CLI-утилиты - изменение поведения в зависимости от режима работы
Советы:
- Для приложений обычно используют
ExitOnError - Для библиотек лучше
ContinueOnError PanicOnErrorредко используется на практике
func (*FlagSet) Float64
func (f *FlagSet) Float64(name string, value float64, usage string) *float64
Float64 определяет флаг float64 с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной float64, в которой хранится значение флага.
func (*FlagSet) Float64Var
func (f *FlagSet) Float64Var(p *float64, name string, value float64, usage string)
Float64Var определяет флаг float64 с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную float64, в которой хранится значение флага.
func (*FlagSet) Func
func (f *FlagSet) Func(name, usage string, fn func(string) error)
Func определяет флаг с указанным именем и строкой использования. Каждый раз, когда флаг встречается, вызывается fn со значением флага. Если fn возвращает ошибку, отличную от nil, она будет рассматриваться как ошибка разбора значения флага.
func (*FlagSet) Init
func (f *FlagSet) Init(name string, errorHandling ErrorHandling)
Init устанавливает имя и свойство обработки ошибок для набора флагов. По умолчанию нулевой FlagSet использует пустое имя и политику обработки ошибок ContinueOnError.
func (*FlagSet) Int
func (f *FlagSet) Int(name string, value int, usage string) *int
Int определяет флаг int с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной int, в которой хранится значение флага.
func (*FlagSet) Int64
func (f *FlagSet) Int64(name string, value int64, usage string) *int64
Int64 определяет флаг int64 с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной int64, в которой хранится значение флага.
func (*FlagSet) Int64Var
func (f *FlagSet) Int64Var(p *int64, name string, value int64, usage string)
Int64Var определяет флаг int64 с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную int64, в которой хранится значение флага.
func (*FlagSet) IntVar
func (f *FlagSet) IntVar(p *int, name string, value int, usage string)
IntVar определяет флаг int с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную int, в которой будет храниться значение флага.
func (*FlagSet) Lookup
func (f *FlagSet) Lookup(name string) *Flag
Lookup возвращает структуру Flag с указанным именем флага, возвращая nil, если такой флаг не существует.
func (*FlagSet) NArg
func (f *FlagSet) NArg() int
NArg — количество аргументов, оставшихся после обработки флагов.
func (*FlagSet) NFlag
func (f *FlagSet) NFlag() int
NFlag возвращает количество установленных флагов.
func (*FlagSet) Name
func (f *FlagSet) Name() string
Name возвращает имя набора флагов.
func (*FlagSet) Output
func (f *FlagSet) Output() io.Writer
Output возвращает место назначения для сообщений об использовании и ошибках. os.Stderr возвращается, если выход не был установлен или был установлен в nil.
func (*FlagSet) Parse
func (f *FlagSet) Parse(arguments []string) error
Parse анализирует определения флагов из списка аргументов, который не должен включать имя команды. Должен вызываться после того, как все флаги в FlagSet определены, и до того, как программа получит доступ к флагам. Возвращаемое значение будет ErrHelp, если -help или -h были установлены, но не определены.
func (*FlagSet) Parsed
func (f *FlagSet) Parsed() bool
Parsed сообщает, был ли вызван f.Parse.
func (*FlagSet) PrintDefaults
func (f *FlagSet) PrintDefaults()
PrintDefaults выводит в стандартный поток ошибок (если не настроено иначе) значения по умолчанию всех определенных флагов командной строки в наборе. Дополнительную информацию см. в документации по глобальной функции PrintDefaults.
func (*FlagSet) Set
func (f *FlagSet) Set(name, value string) error
Set устанавливает значение флага с указанным именем.
func (*FlagSet) SetOutput
func (f *FlagSet) SetOutput(output io.Writer)
SetOutput устанавливает место назначения для сообщений об использовании и ошибках. Если output равно nil, используется os.Stderr.
func (*FlagSet) String
func (f *FlagSet) String(name string, value string, usage string) *string
String определяет строковый флаг с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес строковой переменной, в которой хранится значение флага.
func (*FlagSet) StringVar
func (f *FlagSet) StringVar(p *string, name string, value string, usage string)
StringVar определяет строковый флаг с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на строковую переменную, в которой будет храниться значение флага.
func (*FlagSet) TextVar
func (f *FlagSet) TextVar(p encoding.TextUnmarshaler, name string, value encoding.TextMarshaler, usage string)
TextVar определяет флаг с указанным именем, значением по умолчанию и строкой использования. Аргумент p должен быть указателем на переменную, которая будет хранить значение флага, и p должен реализовывать encoding.TextUnmarshaler. Если флаг используется, его значение будет передано методу UnmarshalText p. Тип значения по умолчанию должен быть таким же, как тип p.
func (*FlagSet) Uint
func (f *FlagSet) Uint(name string, value uint, usage string) *uint
Uint определяет флаг uint с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной uint, которая хранит значение флага.
func (*FlagSet) Uint64
func (f *FlagSet) Uint64(name string, value uint64, usage string) *uint64
Uint64 определяет флаг uint64 с указанным именем, значением по умолчанию и строкой использования. Возвращаемое значение — адрес переменной uint64, в которой хранится значение флага.
func (*FlagSet) Uint64Var
func (f *FlagSet) Uint64Var(p *uint64, name string, value uint64, usage string)
Uint64Var определяет флаг uint64 с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную uint64, в которой хранится значение флага.
func (*FlagSet) UintVar
func (f *FlagSet) UintVar(p *uint, name string, value uint, usage string)
UintVar определяет флаг uint с указанным именем, значением по умолчанию и строкой использования. Аргумент p указывает на переменную uint, в которой будет храниться значение флага.
func (*FlagSet) Var
func (f *FlagSet) Var(value Value, name string, usage string)
Var определяет флаг с указанным именем и строкой использования. Тип и значение флага представлены первым аргументом типа Value, который обычно содержит пользовательскую реализацию Value. Например, вызывающая сторона может создать флаг, который преобразует строку, разделенную запятыми, в набор строк, предоставив набору методы Value; в частности, Set разложит строку, разделенную запятыми, на набор.
func (*FlagSet) Visit
func (f *FlagSet) Visit(fn func(*Flag))
Visit посещает флаги в лексикографическом порядке, вызывая fn для каждого из них. Он посещает только те флаги, которые были установлены.
func (*FlagSet) VisitAll
func (f *FlagSet) VisitAll(fn func(*Flag))
VisitAll посещает флаги в лексикографическом порядке, вызывая fn для каждого из них. Он посещает все флаги, даже те, которые не были установлены.
type Getter
type Getter interface {
Value
Get() any
}
Getter — это интерфейс, который позволяет извлекать содержимое Value. Он оборачивает интерфейс Value, а не является его частью, поскольку появился после Go 1 и его правил совместимости. Все типы Value, предоставляемые этим пакетом, удовлетворяют интерфейсу Getter, за исключением типа, используемого Func.
Пример
Объяснение интерфейса Getter в пакете flag
Интерфейс Getter в пакете flag предоставляет способ получения значения флага в виде interface{} (тип any). Это расширение базового интерфейса Value, добавленное для обеспечения большей гибкости при работе с флагами.
Структура интерфейса:
type Getter interface {
Value
Get() any
}
Где:
Value- стандартный интерфейс для работы с флагами (требует методовString()иSet())Get() any- метод, возвращающий текущее значение флага в виде пустого интерфейса
Пример 1: Проверка значения флага
package main
import (
"flag"
"fmt"
"time"
)
func main() {
// Создаем несколько флагов разных типов
flag.Int("port", 8080, "server port")
flag.Duration("timeout", 5*time.Second, "operation timeout")
flag.String("name", "default", "service name")
flag.Parse()
// Получаем значения через Getter
printFlagValue("port")
printFlagValue("timeout")
printFlagValue("name")
}
func printFlagValue(name string) {
if f := flag.Lookup(name); f != nil {
if getter, ok := f.Value.(flag.Getter); ok {
fmt.Printf("%s: %v (%T)\n", name, getter.Get(), getter.Get())
} else {
fmt.Printf("%s: does not implement Getter\n", name)
}
} else {
fmt.Printf("%s: flag not found\n", name)
}
}
Пример 2: Кастомный тип с Getter
package main
import (
"flag"
"fmt"
"strings"
)
type StringSlice []string
func (s *StringSlice) String() string {
return strings.Join(*s, ",")
}
func (s *StringSlice) Set(value string) error {
*s = append(*s, value)
return nil
}
func (s *StringSlice) Get() any {
return []string(*s)
}
func main() {
var hosts StringSlice
flag.Var(&hosts, "host", "target host (can be specified multiple times)")
flag.Parse()
if g, ok := flag.Lookup("host").Value.(flag.Getter); ok {
fmt.Printf("Hosts: %v\n", g.Get())
}
}
Пример 3: Использование с разными типами флагов
package main
import (
"flag"
"fmt"
"reflect"
)
func main() {
// Разные типы флагов
intFlag := flag.Int("num", 42, "an integer")
boolFlag := flag.Bool("verbose", false, "verbose output")
stringFlag := flag.String("msg", "hello", "a message")
flag.Parse()
// Проверяем реализацию Getter для каждого флага
flags := []string{"num", "verbose", "msg"}
for _, name := range flags {
f := flag.Lookup(name)
if getter, ok := f.Value.(flag.Getter); ok {
val := getter.Get()
fmt.Printf("%s: %v (type: %v)\n", name, val, reflect.TypeOf(val))
}
}
}
Когда использовать Getter:
- Когда нужно работать с флагами разных типов единообразно
- Для создания обобщенных функций обработки флагов
- При реализации кастомных типов флагов с возможностью извлечения значения
Важные замечания:
- Почти все стандартные типы флагов реализуют
Getter - Исключение - флаги, созданные через
flag.Func() - Для проверки реализации используйте type assertion:
if getter, ok := flag.Value.(flag.Getter); ok { value := getter.Get() // ... }
Этот интерфейс особенно полезен при создании сложных CLI-приложений, где требуется гибкая работа с флагами разных типов.
type Value
type Value интерфейс {
String() string
Set(string) error
}
Value — это интерфейс к динамическому значению, хранящемуся в флаге. (Значение по умолчанию представлено в виде строки.)
Если Value имеет метод IsBoolFlag() bool, возвращающий true, парсер командной строки делает -name эквивалентным -name=true, а не использует следующий аргумент командной строки.
Set вызывается один раз, в порядке командной строки, для каждого присутствующего флага. Пакет flag может вызывать метод String с нулевым значением приемника, таким как указатель nil.
Пример
package main
import (
"flag"
"fmt"
"net/url"
)
// URLValue - кастомный тип для парсинга URL в флагах
type URLValue struct {
URL *url.URL // Хранит указатель на разобранный URL
}
// String реализует интерфейс flag.Value
func (v URLValue) String() string {
if v.URL != nil {
return v.URL.String()
}
return ""
}
// Set реализует интерфейс flag.Value
func (v *URLValue) Set(s string) error {
u, err := url.Parse(s)
if err != nil {
return fmt.Errorf("failed to parse URL: %w", err)
}
*v.URL = *u // Копируем разобранный URL
return nil
}
// Get реализует интерфейс flag.Getter
func (v URLValue) Get() any {
return v.URL
}
// Глобальная переменная для хранения разобранного URL
var targetURL = &url.URL{}
func main() {
// Создаем новый набор флагов
fs := flag.NewFlagSet("URLParser", flag.ExitOnError)
// Регистрируем наш кастомный флаг
fs.Var(&URLValue{targetURL}, "url", "URL to parse (e.g. 'https://example.com/path')")
// Парсим аргументы (в реальном приложении используйте os.Args[1:])
err := fs.Parse([]string{"-url", "https://golang.org/pkg/flag/"})
if err != nil {
fmt.Printf("Error parsing flags: %v\n", err)
return
}
// Выводим разобранные компоненты URL
fmt.Printf("Parsed URL components:\n")
fmt.Printf("{scheme: %q, host: %q, path: %q}\n",
targetURL.Scheme,
targetURL.Host,
targetURL.Path)
}
Комментарии по коду:
Интерфейс Value:
String()используется для вывода текущего значенияSet()парсит строку и сохраняет значение
Интерфейс Getter:
- Позволяет получить значение как
interface{} - Полезно для обобщенной обработки флагов
- Позволяет получить значение как
Особенности реализации:
- Используем указатель на
url.URLдля модификации - Метод
Set()должен быть с pointer receiver - Всегда проверяем ошибки парсинга URL
- Используем указатель на
Использование в реальных приложениях:
// Вместо fs.Parse([]string{...}) используйте: fs.Parse(os.Args[1:])Дополнительные улучшения:
- Можно добавить валидацию URL (например, требовать HTTPS)
- Можно добавить метод
IsSet()для проверки установки значения
Пример запуска:
go run main.go -url "https://example.com/api/v1?param=value"
Вывод:
Parsed URL components:
{scheme: "https", host: "example.com", path: "/api/v1"}
Этот код теперь представляет собой надежную реализацию кастомного флага для работы с URL в Go-приложениях.
8 - Полный справочник по пакету fmt в Go
Пакет fmt (format) — один из самых часто используемых в Go. Он предоставляет функции для форматированного ввода/вывода, аналогичные printf и scanf в C.
1. Основные функции вывода
1.1. Вывод в стандартный поток (stdout)
fmt.Print("Hello, ", "World!") // Без перевода строки
fmt.Println("Hello, World!") // С переводом строки
fmt.Printf("Name: %s, Age: %d\n", "Alice", 25) // Форматированный вывод
1.2. Вывод в строку (Sprint, Sprintf, Sprintln)
s1 := fmt.Sprint("Hello, ", "World!") // => "Hello, World!"
s2 := fmt.Sprintf("Name: %s, Age: %d", "Bob", 30) // => "Name: Bob, Age: 30"
s3 := fmt.Sprintln("Line with newline") // => "Line with newline\n"
1.3. Вывод в файл или io.Writer (Fprint, Fprintf, Fprintln)
file, _ := os.Create("output.txt")
fmt.Fprint(file, "Hello, file!") // Запись в файл
fmt.Fprintf(file, "Value: %v", 42) // Форматированная запись
fmt.Fprintln(file, "End of line") // Запись с переводом строки
2. Форматирование вывода (Printf, Sprintf, Fprintf)
2.1. Основные спецификаторы
| Спецификатор | Описание | Пример (%v) | Вывод (42) |
|---|---|---|---|
%v | Универсальный формат | %v | 42 |
%+v | С полями структур | %+v | {X:42} |
%#v | Go-синтаксис | %#v | 42 (int) |
%T | Тип переменной | %T | int |
%% | Символ % | %% | % |
2.2. Форматирование разных типов данных
// Целые числа
fmt.Printf("%d %b %o %x %X\n", 42, 42, 42, 42, 42) // 42 101010 52 2a 2A
// Вещественные числа
fmt.Printf("%f %.2f %e %g\n", 3.1415, 3.1415, 3.1415, 3.1415) // 3.141500 3.14 3.141500e+00 3.1415
// Строки и символы
fmt.Printf("%s %q %c\n", "Go", "Go", 'G') // Go "Go" G
// Логические значения
fmt.Printf("%t\n", true) // true
2.3. Ширина и точность
// Ширина поля (выравнивание)
fmt.Printf("|%5d|%-5d|\n", 42, 42) // | 42|42 |
// Точность для float
fmt.Printf("%.2f\n", 3.141592) // 3.14
// Комбинация ширины и точности
fmt.Printf("%10.2f\n", 3.141592) // " 3.14"
2.4. Позиционные аргументы
fmt.Printf("%[2]d %[1]d\n", 10, 20) // 20 10
3. Ввод данных (Scan, Scanf, Scanln)
3.1. Чтение из стандартного ввода (stdin)
var name string
var age int
fmt.Print("Enter name and age: ")
fmt.Scan(&name, &age) // Читает до пробела/перевода строки
fmt.Printf("Hello, %s (%d)\n", name, age)
3.2. Форматированный ввод (Scanf)
var a, b int
fmt.Scanf("%d,%d", &a, &b) // Ввод "10,20" → a=10, b=20
3.3. Чтение строки (Scanln)
var input string
fmt.Scanln(&input) // Читает до перевода строки
3.4. Чтение из строки (Sscan, Sscanf, Sscanln)
var x, y int
fmt.Sscanf("10 20", "%d %d", &x, &y) // x=10, y=20
3.5. Чтение из файла (Fscan, Fscanf, Fscanln)
file, _ := os.Open("input.txt")
var value int
fmt.Fscanf(file, "%d", &value) // Чтение числа из файла
4. Дополнительные функции
4.1. Форматирование ошибок (Errorf)
err := fmt.Errorf("invalid value: %d", 42) // Создает error
4.2. Вывод в io.Writer с форматированием (Fprintf)
var buf bytes.Buffer
fmt.Fprintf(&buf, "Value: %d", 42) // Запись в буфер
4.3. Сканирование из io.Reader (Fscanf)
reader := strings.NewReader("100")
var num int
fmt.Fscanf(reader, "%d", &num) // num = 100
5. Полезные примеры
5.1. Вывод таблицы
fmt.Printf("|%-10s|%10s|\n", "Name", "Age")
fmt.Printf("|%-10s|%10d|\n", "Alice", 25)
fmt.Printf("|%-10s|%10d|\n", "Bob", 30)
Вывод:
|Name | Age|
|Alice | 25|
|Bob | 30|
5.2. Чтение нескольких значений
var a, b int
fmt.Print("Enter two numbers: ")
fmt.Scan(&a, &b)
fmt.Println("Sum:", a+b)
5.3. Форматирование JSON
data := map[string]interface{}{"name": "Alice", "age": 25}
jsonStr := fmt.Sprintf("%+v", data) // {"age":25, "name":"Alice"}
Дополнительный раздел: Форматирование различных типов данных в Go
1. Форматирование базовых типов (Printf, Sprintf, Fprintf)
1.1. Целые числа (int, int32, int64, uint)
num := 42
fmt.Printf("Десятичное: %d\n", num) // 42
fmt.Printf("Двоичное: %b\n", num) // 101010
fmt.Printf("Восьмеричное: %o\n", num) // 52
fmt.Printf("Шестнадцатеричное (нижний регистр): %x\n", num) // 2a
fmt.Printf("Шестнадцатеричное (верхний регистр): %X\n", num) // 2A
fmt.Printf("С ведущими нулями: %05d\n", num) // 00042
1.2. Вещественные числа (float32, float64)
pi := 3.1415926535
fmt.Printf("По умолчанию: %f\n", pi) // 3.141593
fmt.Printf("С точностью 2 знака: %.2f\n", pi) // 3.14
fmt.Printf("Экспоненциальная запись: %e\n", pi) // 3.141593e+00
fmt.Printf("Автовыбор формата: %g\n", pi) // 3.1415926535
fmt.Printf("Ширина 10 символов: %10.2f\n", pi) // " 3.14"
1.3. Строки (string) и символы (rune)
str := "Hello"
fmt.Printf("Строка: %s\n", str) // Hello
fmt.Printf("Строка в кавычках: %q\n", str) // "Hello"
fmt.Printf("Ширина 10 символов: %10s\n", str) // " Hello"
fmt.Printf("Символ: %c\n", 'A') // A
fmt.Printf("Юникод-символ: %U\n", 'Я') // U+042F
1.4. Логические значения (bool)
flag := true
fmt.Printf("Логическое: %t\n", flag) // true
1.5. Указатели (*int, *string)
x := 42
ptr := &x
fmt.Printf("Указатель: %p\n", ptr) // 0xc0000180a8
fmt.Printf("Значение по указателю: %v\n", *ptr) // 42
2. Форматирование структур (struct) и массивов (slice, array)
2.1. Вывод структуры
type Person struct {
Name string
Age int
}
p := Person{"Alice", 25}
fmt.Printf("Универсальный формат: %v\n", p) // {Alice 25}
fmt.Printf("С полями: %+v\n", p) // {Name:Alice Age:25}
fmt.Printf("Go-синтаксис: %#v\n", p) // main.Person{Name:"Alice", Age:25}
2.2. Форматирование слайсов и массивов
arr := [3]int{1, 2, 3}
slice := []string{"a", "b", "c"}
fmt.Printf("Массив: %v\n", arr) // [1 2 3]
fmt.Printf("Слайс: %v\n", slice) // [a b c]
fmt.Printf("Go-синтаксис: %#v\n", slice) // []string{"a", "b", "c"}
3. Форматирование с помощью fmt.Append (Go 1.19+)
Функция fmt.Append позволяет форматировать данные и добавлять их в слайс байт ([]byte).
3.1. Базовое использование
buf := []byte("Данные: ")
buf = fmt.Append(buf, 42, " ", true)
fmt.Println(string(buf)) // Данные: 42 true
3.2. Форматированный вывод (Appendf)
buf := []byte("Число: ")
buf = fmt.Appendf(buf, "%05d", 42)
fmt.Println(string(buf)) // Число: 00042
3.3. Добавление в буфер (bytes.Buffer)
var buffer bytes.Buffer
buffer.WriteString("Строка: ")
fmt.Fprintf(&buffer, "%q", "Hello")
fmt.Println(buffer.String()) // Строка: "Hello"
4. Специальные форматы
4.1. Вывод времени (time.Time)
t := time.Now()
fmt.Printf("RFC3339: %v\n", t.Format(time.RFC3339)) // 2024-03-15T14:20:10Z
fmt.Printf("Кастомный формат: %02d.%02d.%04d\n", t.Day(), t.Month(), t.Year()) // 15.03.2024
4.2. Форматирование JSON
data := map[string]any{"name": "Alice", "age": 25}
jsonStr := fmt.Sprintf("%+v", data) // map[age:25 name:Alice]
4.3. Ширина и выравнивание
fmt.Printf("|%10s|\n", "left") // | left|
fmt.Printf("|%-10s|\n", "right") // |right |
fmt.Printf("|%10.2f|\n", 3.1415) // | 3.14|
5. Полезные примеры
5.1. Таблица с выравниванием
fmt.Printf("|%-10s|%10s|\n", "Name", "Age")
fmt.Printf("|%-10s|%10d|\n", "Alice", 25)
fmt.Printf("|%-10s|%10d|\n", "Bob", 30)
Вывод:
|Name | Age|
|Alice | 25|
|Bob | 30|
5.2. Форматирование ошибок
err := fmt.Errorf("ошибка: значение %d недопустимо", 100)
fmt.Println(err) // ошибка: значение 100 недопустимо
5.3. Чтение и запись в файл
// Запись
file, _ := os.Create("data.txt")
fmt.Fprintf(file, "Число: %d", 42)
file.Close()
// Чтение
file, _ = os.Open("data.txt")
var num int
fmt.Fscanf(file, "Число: %d", &num)
fmt.Println(num) // 42
9 - Описание пакета io языка программирования Go
Его основная задача - обернуть существующие реализации таких примитивов, например, в пакете os, в общие публичные интерфейсы, которые абстрагируют эту функциональность, а также некоторые другие связанные с ней примитивы.
Поскольку эти интерфейсы и примитивы обертывают операции нижнего уровня с различными реализациями, если клиенты не проинформированы об ином, не следует полагать, что они безопасны для параллельного выполнения.
Константы
const (
SeekStart = 0 // поиск относительно начала файла
SeekCurrent = 1 // поиск относительно текущего смещения
SeekEnd = 2 // поиск относительно конца)
Значения whence поиска.
Переменные
var EOF = errors.New(«EOF»)
EOF — это ошибка, возвращаемая Read, когда входные данные больше не доступны. (Read должен возвращать сам EOF, а не ошибку, оборачивающую EOF, потому что вызывающие функции будут проверять EOF с помощью ==.) Функции должны возвращать EOF только для сигнализации о корректном завершении ввода. Если EOF возникает неожиданно в структурированном потоке данных, соответствующей ошибкой является либо ErrUnexpectedEOF, либо какая-либо другая ошибка, дающая более подробную информацию.
var ErrClosedPipe = errors.New(«io: read/write on closed pipe»)
ErrClosedPipe — это ошибка, используемая для операций чтения или записи в закрытом канале.
var ErrNoProgress = errors.New(«multiple Read calls return no data or error»)
ErrNoProgress возвращается некоторыми клиентами Reader, когда много вызовов Read не возвращают никаких данных или ошибок, что обычно является признаком неисправной реализации Reader.
var ErrShortBuffer = errors.New(«короткий буфер»)
ErrShortBuffer означает, что для чтения потребовался буфер большего размера, чем был предоставлен.
var ErrShortWrite = errors.New(«короткая запись»)
ErrShortWrite означает, что запись приняла меньше байтов, чем было запрошено, но не вернула явную ошибку.
var ErrUnexpectedEOF = errors.New(«неожиданный EOF»)
ErrUnexpectedEOF означает, что EOF был обнаружен в середине чтения блока фиксированного размера или структуры данных.
9.1 - Описание функций пакета io
func Copy
func Copy(dst Writer, src Reader) (written int64, err error)
Copy копирует данные из src в dst до тех пор, пока не будет достигнут EOF в src или не произойдет ошибка. Функция возвращает количество скопированных байтов и первую ошибку, возникшую при копировании, если таковая имеется.
Успешная функция Copy возвращает err == nil, а не err == EOF. Поскольку функция Copy определена для чтения из src до EOF, она не рассматривает EOF из Read как ошибку, о которой следует сообщать.
Если 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)
}
}
Output:
some io.Reader stream to be read
func 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 is used here...
if _, err := io.CopyBuffer(os.Stdout, r1, buf); err != nil {
log.Fatal(err)
}
// ... reused here also. No need to allocate an extra buffer.
if _, err := io.CopyBuffer(os.Stdout, r2, buf); err != nil {
log.Fatal(err)
}
}
Output:
first reader
second reader
func 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, 4); err != nil {
log.Fatal(err)
}
}
Output:
some
func Pipe
func Pipe() (*PipeReader, *PipeWriter)
Pipe создает синхронный конвейер в памяти. Его можно использовать для соединения кода, ожидающего io.Reader, с кодом, ожидающим io.Writer.
Чтение и запись в канале сопоставляются один к одному, за исключением случаев, когда для потребления одной записи требуется несколько чтений. То есть каждая запись в PipeWriter блокируется до тех пор, пока не будет удовлетворено одно или несколько чтений из PipeReader, которые полностью потребляют записанные данные. Данные копируются непосредственно из записи в соответствующее чтение (или чтения); внутренней буферизации нет.
Безопасно вызывать Read и Write параллельно друг с другом или с Close. Параллельные вызовы Read и параллельные вызовы Write также безопасны: отдельные вызовы будут последовательно блокироваться.
Пример
package main
import (
"fmt"
"io"
"log"
"os"
)
func main() {
r, w := io.Pipe()
go func() {
fmt.Fprint(w, "some io.Reader stream to be read\n")
w.Close()
}()
if _, err := io.Copy(os.Stdout, r); err != nil {
log.Fatal(err)
}
}
Output:
some io.Reader stream to be read
func ReadAll
func ReadAll(r Reader) ([]byte, error)
ReadAll читает из r до ошибки или EOF и возвращает прочитанные данные. Успешный вызов возвращает err == nil, а не err == EOF. Поскольку ReadAll определено для чтения из src до EOF, оно не рассматривает EOF из Read как ошибку, о которой следует сообщать.
Пример
package main
import (
"fmt"
"io"
"log"
"strings"
)
func main() {
r := strings.NewReader("Go is a general-purpose language designed with systems programming in mind.")
b, err := io.ReadAll(r)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", b)
}
Output:
Go is a general-purpose language designed with systems programming in mind.
func ReadAtLeast
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)
ReadAtLeast читает из r в buf, пока не прочитает как минимум min байт. Он возвращает количество скопированных байтов и ошибку, если было прочитано меньше байтов. Ошибка EOF возникает только в том случае, если не было прочитано ни одного байта. Если EOF возникает после чтения менее min байтов, ReadAtLeast возвращает ErrUnexpectedEOF. Если min больше длины buf, 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)
// buffer smaller than minimal read size.
shortBuf := make([]byte, 3)
if _, err := io.ReadAtLeast(r, shortBuf, 4); err != nil {
fmt.Println("error:", err)
}
// minimal read size bigger than io.Reader stream
longBuf := make([]byte, 64)
if _, err := io.ReadAtLeast(r, longBuf, 64); err != nil {
fmt.Println("error:", err)
}
}
Output:
some io.Reader
error: short buffer
error: unexpected EOF
func 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)
// minimal read size bigger than io.Reader stream
longBuf := make([]byte, 64)
if _, err := io.ReadFull(r, longBuf); err != nil {
fmt.Println("error:", err)
}
}
Output:
some
error: unexpected EOF
func WriteString
func WriteString(w Writer, s string) (n int, err error)
WriteString записывает содержимое строки s в w, который принимает набор байтов. Если w реализует StringWriter, [StringWriter.WriteString] вызывается напрямую. В противном случае [Writer.Write] вызывается ровно один раз.
Пример
package main
import (
"io"
"log"
"os"
)
func main() {
if _, err := io.WriteString(os.Stdout, "Hello World"); err != nil {
log.Fatal(err)
}
}
Output:
Hello World
9.2 - Описание типов пакета io языка программирования GO
type ByteReader
type ByteReader interface {
ReadByte() (byte, error)
}
ByteReader — это интерфейс, который оборачивает метод ReadByte.
ReadByte считывает и возвращает следующий байт из ввода или любую возникшую ошибку. Если ReadByte возвращает ошибку, входной байт не был обработан, и возвращаемое значение байта не определено.
ReadByte предоставляет эффективный интерфейс для обработки по одному байту за раз. Reader, который не реализует ByteReader, можно обернуть с помощью bufio.NewReader, чтобы добавить этот метод.
type ByteScanner
type ByteScanner interface {
ByteReader
UnreadByte() error
}
ByteScanner — это интерфейс, который добавляет метод UnreadByte к базовому методу ReadByte.
UnreadByte заставляет следующий вызов ReadByte возвращать последний прочитанный байт. Если последняя операция не была успешным вызовом ReadByte, UnreadByte может вернуть ошибку, не прочитать последний прочитанный байт (или байт, предшествующий последнему непрочитанному байту), или (в реализациях, поддерживающих интерфейс Seeker) перейти на один байт перед текущим смещением.
type ByteWriter
type ByteWriter interface {
WriteByte(c byte) error
}
ByteWriter — это интерфейс, который оборачивает метод WriteByte.
type Closer
type Closer interface {
Close() error
}
Closer — это интерфейс, который оборачивает базовый метод Close.
Поведение Close после первого вызова не определено. Конкретные реализации могут документировать свое собственное поведение.
type LimitedReader
type LimitedReader struct {
R Reader // базовый читатель
N int64 // максимальное количество оставшихся байтов
}
LimitedReader читает из R, но ограничивает количество возвращаемых данных до N байтов. Каждый вызов Read обновляет N, чтобы отразить новое количество оставшихся данных. Read возвращает EOF, когда N <= 0 или когда базовый R возвращает EOF.
func (*LimitedReader) Read
func (l *LimitedReader) Read(p []byte) (n int, err error)
Пример
package main
import (
"fmt"
"io"
"strings"
)
func main() {
// Создаем источник данных - строка длиной 100 символов
data := strings.Repeat("abcdefghij", 10) // 100 байт
reader := strings.NewReader(data)
// Создаем LimitedReader, который прочитает только первые 35 байт
limitedReader := &io.LimitedReader{
R: reader, // базовый reader
N: 35, // лимит в 35 байт
}
// Буфер для чтения
buf := make([]byte, 10) // читаем по 10 байт за раз
var totalRead int
for {
// Читаем данные
n, err := limitedReader.Read(buf)
if err != nil {
if err == io.EOF {
fmt.Println("\nДостигнут лимит или конец данных")
} else {
fmt.Println("\nОшибка чтения:", err)
}
break
}
totalRead += n
fmt.Printf("Прочитано %d байт: %q\n", n, buf[:n])
fmt.Printf("Осталось прочитать: %d байт\n", limitedReader.N)
}
fmt.Println("Всего прочитано:", totalRead, "байт")
fmt.Println("Осталось данных в основном reader:", reader.Len(), "байт")
}
Прочитано 10 байт: "abcdefghij"
Осталось прочитать: 25 байт
Прочитано 10 байт: "abcdefghij"
Осталось прочитать: 15 байт
Прочитано 10 байт: "abcdefghij"
Осталось прочитать: 5 байт
Прочитано 5 байт: "abcde"
Осталось прочитать: 0 байт
Достигнут лимит или конец данных
Всего прочитано: 35 байт
Осталось данных в основном reader: 65 байт
type OffsetWriter
type OffsetWriter struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Объяснение OffsetWriter
OffsetWriter в Go — это обёртка вокруг WriterAt, которая позволяет записывать данные, начиная с указанного смещения в базовом потоке. Он автоматически управляет позицией записи и предоставляет три ключевые функции: Write (пишет с текущей позиции), WriteAt (пишет по конкретному смещению без изменения позиции) и Seek (меняет текущую позицию). Это особенно полезно для записи в определённые места файлов (например, при обновлении заголовков или работе с бинарными форматами), когда нужно контролировать точное расположение данных без ручного управления позицией в базовом WriterAt.
Пример с использованием всех методов OffsetWriter:
package main
import (
"fmt"
"io"
"os"
)
func main() {
// Создаем временный файл
tmpFile, err := os.CreateTemp("", "offset-writer-demo")
if err != nil {
panic(err)
}
defer os.Remove(tmpFile.Name())
defer tmpFile.Close()
// Заполняем файл пробельными символами для наглядности
_, err = tmpFile.WriteString("=== Начало файла ===\n" +
strings.Repeat(".", 100) + "\n=== Конец файла ===")
if err != nil {
panic(err)
}
// Создаем OffsetWriter с начальным смещением 20 байт
ow := io.NewOffsetWriter(tmpFile, 20)
// 1. Используем Write - пишет с текущего смещения
fmt.Println("\n1. Запись через Write()")
_, err = ow.Write([]byte("ПЕРВАЯ ЗАПИСЬ"))
if err != nil {
panic(err)
}
printFileContent(tmpFile.Name())
// 2. Используем Seek для изменения позиции
fmt.Println("\n2. Seek(10, io.SeekCurrent)")
newPos, err := ow.Seek(10, io.SeekCurrent) // Перемещаемся на +10 байт от текущей позиции
if err != nil {
panic(err)
}
fmt.Printf("Новая позиция: %d\n", newPos)
// 3. Еще одна запись через Write
_, err = ow.Write([]byte("ВТОРАЯ ЗАПИСЬ"))
if err != nil {
panic(err)
}
printFileContent(tmpFile.Name())
// 4. Используем WriteAt - пишет по абсолютному смещению (не меняет текущую позицию)
fmt.Println("\n4. WriteAt() по смещению 5")
_, err = ow.WriteAt([]byte("ТРЕТЬЯ"), 5)
if err != nil {
panic(err)
}
printFileContent(tmpFile.Name())
// 5. Проверяем текущую позицию после WriteAt
pos, _ := ow.Seek(0, io.SeekCurrent)
fmt.Printf("\n5. Текущая позиция после WriteAt: %d\n", pos)
// 6. Возвращаемся в начало и пишем еще
fmt.Println("\n6. Seek(0, io.SeekStart) + Write()")
ow.Seek(0, io.SeekStart)
ow.Write([]byte("НАЧАЛО"))
printFileContent(tmpFile.Name())
}
func printFileContent(filename string) {
data, _ := os.ReadFile(filename)
fmt.Println("Содержимое файла:")
fmt.Println(string(data))
fmt.Println("Длина:", len(data), "байт")
fmt.Println(strings.Repeat("-", 50))
}
Разбор функциональности:
Write(p []byte):
- Записывает данные с текущей позиции
- Автоматически увеличивает текущую позицию
Seek(offset, whence):
whenceможет быть:io.SeekStart- от начала файлаio.SeekCurrent- от текущей позицииio.SeekEnd- от конца файла
- Возвращает новую позицию
WriteAt(p []byte, off int64):
- Записывает данные по абсолютному смещению
off - Не изменяет текущую позицию записи
- Полезен для точечных модификаций
- Записывает данные по абсолютному смещению
Пример вывода:
1. Запись через Write()
Содержимое файла:
=== Начало файла ===
ПЕРВАЯ ЗАПИСЬ....................
=== Конец файла ===
Длина: 132 байт
--------------------------------------------------
2. Seek(10, io.SeekCurrent)
Новая позиция: 42
3. Запись через Write()
Содержимое файла:
=== Начало файла ===
ПЕРВАЯ ЗАПИСЬ..........ВТОРАЯ ЗАПИСЬ.....
=== Конец файла ===
Длина: 154 байт
--------------------------------------------------
4. WriteAt() по смещению 5
Содержимое файла:
=== Начало файла ===
ПЕРТЬЯ ЗАПИСЬ..........ВТОРАЯ ЗАПИСЬ.....
=== Конец файла ===
Длина: 154 байт
--------------------------------------------------
5. Текущая позиция после WriteAt: 42
6. Seek(0, io.SeekStart) + Write()
Содержимое файла:
=== Начало файла ===
НАЧАЛО ЗАПИСЬ..........ВТОРАЯ ЗАПИСЬ.....
=== Конец файла ===
Длина: 154 байт
--------------------------------------------------
Практические сценарии использования:
Редактирование файлов:
// Исправление заголовка в существующем файле ow := io.NewOffsetWriter(file, 0) ow.WriteAt(correctHeader, 0)Работа с бинарными форматами:
// Запись данных в определенные секции файла ow.Seek(headerSize, io.SeekStart) ow.Write(dataChunk)Многопоточная запись:
// Каждая горутина пишет в свой раздел go func() { sectionWriter := io.NewOffsetWriter(file, sectionOffset) sectionWriter.Write(sectionData) }()
func NewOffsetWriter
func NewOffsetWriter(w WriterAt, off int64) *OffsetWriter
NewOffsetWriter возвращает OffsetWriter, который записывает в w, начиная с смещения off.
func (*OffsetWriter) Seek
func (o *OffsetWriter) Seek(offset int64, whence int) (int64, error)
func (*OffsetWriter) Write
func (o *OffsetWriter) Write(p []byte) (n int, err error)
func (*OffsetWriter) WriteAt
func (o *OffsetWriter) WriteAt(p []byte, off int64) (n int, err error)
type PipeReader
type PipeReader struct {
// содержит отфильтрованные или неэкспортируемые поля
}
PipeReader — это читающая часть канала (pipe).
Объяснение PipeReader и PipeWriter
PipeReader представляет читающую часть именованного канала (pipe).
PipeWriter представляет записывающую часть именованного канала (pipe).
package main
import (
"fmt"
"io"
"time"
)
func main() {
// Создаем pipe
pipeReader, pipeWriter := io.Pipe()
// Горутина для чтения из канала
go func() {
buf := make([]byte, 256)
// 1. Обычное чтение
n, err := pipeReader.Read(buf)
if err != nil {
fmt.Printf("Ошибка чтения: %v\n", err)
return
}
fmt.Printf("Прочитано %d байт: %s\n", n, buf[:n])
// 2. Попытка чтения после закрытия записи
n, err = pipeReader.Read(buf)
if err == io.EOF {
fmt.Println("Канал закрыт (EOF)")
} else if err != nil {
fmt.Printf("Ошибка чтения: %v\n", err)
}
// 3. Чтение после CloseWithError
_, err = pipeReader.Read(buf)
if err != nil {
fmt.Printf("Ошибка после CloseWithError: %v\n", err)
}
}()
// Горутина для записи в канал
go func() {
// Записываем данные
_, err := pipeWriter.Write([]byte("Тестовые данные"))
if err != nil {
fmt.Printf("Ошибка записи: %v\n", err)
}
// Закрываем запись (обычное закрытие)
pipeWriter.Close()
// Даем время на обработку
time.Sleep(100 * time.Millisecond)
// Пытаемся записать после закрытия чтения
_, err = pipeWriter.Write([]byte("Новые данные"))
if err == io.ErrClosedPipe {
fmt.Println("Попытка записи в закрытый канал (ErrClosedPipe)")
}
}()
// Даем время на выполнение
time.Sleep(200 * time.Millisecond)
// Закрываем читатель с ошибкой
err := pipeReader.CloseWithError(fmt.Errorf("кастомная ошибка"))
if err != nil {
fmt.Printf("Ошибка при CloseWithError: %v\n", err)
}
// Пытаемся записать после CloseWithError
_, err = pipeWriter.Write([]byte("Последние данные"))
if err != nil {
fmt.Printf("Ошибка записи после CloseWithError: %v\n", err)
}
time.Sleep(100 * time.Millisecond)
}
Разбор методов:
Read():
- Блокируется, пока не появятся данные или не закроется записывающая часть
- При закрытии записывающей части возвращает
io.EOF - При
CloseWithErrorвозвращает указанную ошибку
Close():
- Закрывает читающую часть
- Последующие записи будут возвращать
io.ErrClosedPipe
CloseWithError():
- Закрывает читающую часть с указанной ошибкой
- Последующие операции чтения вернут эту ошибку
- Не перезаписывает предыдущие ошибки
Пример вывода:
Прочитано 28 байт: Тестовые данные
Канал закрыт (EOF)
Попытка записи в закрытый канал (ErrClosedPipe)
Ошибка после CloseWithError: кастомная ошибка
Ошибка записи после CloseWithError: io: read/write on closed pipe
Практическое применение:
- Связь между горутинами
- Преобразование данных “на лету”
- Тестирование кода, работающего с интерфейсами
io.Reader/io.Writer - Реализация прокси-серверов и middleware
func (*PipeReader) Close
func (r *PipeReader) Close() error
Close закрывает читатель; последующие записи в записывающую половину канала будут возвращать ошибку ErrClosedPipe.
func (*PipeReader) CloseWithError
func (r *PipeReader) CloseWithError(err error) error
CloseWithError закрывает читатель; последующие записи в записывающую половину канала будут возвращать ошибку err.
CloseWithError никогда не перезаписывает предыдущую ошибку, если она существует, и всегда возвращает nil.
func (*PipeReader) Read
func (r *PipeReader) Read(data []byte) (n int, err error)
Read реализует стандартный интерфейс Read: он считывает данные из канала, блокируя до тех пор, пока не появится записывающее устройство или не будет закрыта записывающая часть. Если записывающая часть закрыта с ошибкой, эта ошибка возвращается как err; в противном случае err равен EOF.
type PipeWriter
type PipeWriter struct {
// содержит отфильтрованные или неэкспортируемые поля
}
PipeWriter — это записывающая часть канала (pipe).
func (*PipeWriter) Close
func (w *PipeWriter) Close() error
Close закрывает записывающее устройство; последующие чтения из читающей половины канала не будут возвращать байты и EOF.
func (*PipeWriter) CloseWithError
func (w *PipeWriter) CloseWithError(err error) error
CloseWithError закрывает запись; последующие чтения из части трубы, отвечающей за чтение, не будут возвращать байты и будут возвращать ошибку err или EOF, если err равна nil.
CloseWithError никогда не перезаписывает предыдущую ошибку, если она существует, и всегда возвращает nil.
func (*PipeWriter) Write
func (w *PipeWriter) Write(data []byte) (n int, err error)
Write реализует стандартный интерфейс Write: он записывает данные в канал, блокируя его до тех пор, пока один или несколько читателей не потребят все данные или читающая сторона не будет закрыта. Если читающая сторона закрыта с ошибкой, эта ошибка возвращается как err; в противном случае err равна ErrClosedPipe.
type ReadCloser
type ReadCloser интерфейс {
Reader
Closer
}
ReadCloser — это интерфейс, который группирует основные методы Read и Close.
Объяснение ReadCloser
ReadCloser - это просто комбинация двух возможностей:
- Чтение данных (как у обычного
Reader) - Закрытие ресурса (как у
Closer)
Это интерфейс для объектов, которые:
- Могут отдавать данные (например, файл, сетевое соединение)
- И требуют закрытия после использования (чтобы освободить ресурсы)
Примеры использования:
- Открыли файл → читаем из него → закрываем
- Установили сетевое соединение → получаем данные → разрываем соединение
NopCloser - это “адаптер”:
- Берет обычный
Reader(который не умеет закрываться) - Возвращает
ReadCloser, где методClose()ничего не делает (“no-op” - операция-пустышка)
Зачем это нужно?
Когда функция требует ReadCloser, а у вас есть только Reader (который не нужно закрывать)
Пример:
// Есть строка (она реализует Reader)
data := strings.NewReader("Привет, мир!")
// Но нам нужно передать ReadCloser
rc := io.NopCloser(data)
// Теперь можно использовать везде, где требуется ReadCloser
// При вызове rc.Close() ничего не произойдет
Где применяется:
- Когда работаем с API, требующим
ReadCloser - Когда нужно подставить “фейковый” closer для тестирования
- При работе с данными в памяти, которые не требуют очистки
func NopCloser
func NopCloser(r Reader) ReadCloser
NopCloser возвращает ReadCloser с методом Close, не выполняющим никаких действий, который оборачивает предоставленный Reader r. Если r реализует WriterTo, возвращаемый ReadCloser будет реализовывать WriterTo, перенаправляя вызовы r.
type ReadSeekCloser
type ReadSeekCloser интерфейс {
Reader
Seeker
Closer
}
ReadSeekCloser — интерфейс, который группирует основные методы Read, Seek и Close.
Объяснение ReadSeekCloser, ReadSeeker, ReadWriteCloser…
1. ReadSeekCloser
Назначение: Объединяет чтение, произвольный доступ и закрытие ресурса.
Пример с файлом:
func processFile(rsc io.ReadSeekCloser) error {
data := make([]byte, 100)
// Чтение
if _, err := rsc.Read(data); err != nil {
return err
}
// Перемещение в начало
if _, err := rsc.Seek(0, io.SeekStart); err != nil {
return err
}
// Закрытие
defer rsc.Close()
return nil
}
// Использование
file, _ := os.Open("data.txt")
processFile(file) // *os.File реализует ReadSeekCloser
Зачем: Нужен для работы с ресурсами (файлы, сетевые потоки), где требуется:
- Чтение данных
- Перемещение по содержимому
- Обязательное закрытие
2. ReadSeeker
Назначение: Чтение + перемещение по данным без закрытия.
Пример с буфером:
func analyze(data io.ReadSeeker) {
// Первое чтение
buf := make([]byte, 10)
data.Read(buf)
// Возврат в начало
data.Seek(0, io.SeekStart)
}
// Использование
buffer := strings.NewReader("abcdefghij")
analyze(buffer) // strings.Reader реализует ReadSeeker
Зачем: Для данных, где нужно:
- Многократное чтение
- Навигация (например, парсинг заголовков)
3. ReadWriteCloser
Назначение: Чтение + запись + закрытие.
Пример с сетевым соединением:
func handleConnection(conn io.ReadWriteCloser) {
defer conn.Close()
// Чтение
buf := make([]byte, 1024)
conn.Read(buf)
// Ответ
conn.Write([]byte("OK"))
}
// Использование (псевдокод)
// conn, _ := net.Dial("tcp", "example.com:80")
// handleConnection(conn) // net.Conn реализует ReadWriteCloser
Зачем: Для двусторонних ресурсов:
- Файлы с записью
- Сетевые соединения
- Драйверы устройств
4. ReadWriteSeeker
Назначение: Чтение + запись + навигация.
Пример с файлом логов:
func updateLog(rws io.ReadWriteSeeker, msg string) {
// Перемещение в конец
rws.Seek(0, io.SeekEnd)
// Запись
rws.Write([]byte(msg))
}
// Использование
file, _ := os.OpenFile("log.txt", os.O_RDWR, 0644)
updateLog(file, "New entry\n")
Зачем: Для редактируемых ресурсов:
- Файлы баз данных
- Логи
- Память с произвольным доступом
5. ReadWriter
Назначение: Только чтение + запись.
Пример с буфером в памяти:
func process(rw io.ReadWriter) {
rw.Write([]byte("ping"))
response, _ := io.ReadAll(rw)
fmt.Println(string(response))
}
// Использование
var buf bytes.Buffer
buf.WriteString("pong")
process(&buf) // bytes.Buffer реализует ReadWriter
Зачем: Для простых двусторонних потоков:
- Буферы
- Шифрование/сжатие “на лету”
- Тестирование
Ключевые отличия:
| Тип | Read | Write | Seek | Close | Примеры использования |
|---|---|---|---|---|---|
ReadSeekCloser | ✓ | ✗ | ✓ | ✓ | Файлы, сетевые потоки |
ReadSeeker | ✓ | ✗ | ✓ | ✗ | Парсинг данных |
ReadWriteCloser | ✓ | ✓ | ✗ | ✓ | Сокеты, открытые файлы на запись |
ReadWriteSeeker | ✓ | ✓ | ✓ | ✗ | Файлы БД, лог-файлы |
ReadWriter | ✓ | ✓ | ✗ | ✗ | Буферы, преобразователи данных |
type ReadSeeker
type ReadSeeker интерфейс {
Reader
Seeker
}
ReadSeeker — интерфейс, который группирует основные методы Read и Seek.
type ReadWriteCloser
type ReadWriteCloser интерфейс {
Reader
Writer
Closer
}
ReadWriteCloser — это интерфейс, который группирует основные методы Read, Write и Close.
type ReadWriteSeeker
type ReadWriteSeeker интерфейс {
Reader
Writer
Seeker
}
ReadWriteSeeker — интерфейс, который группирует основные методы Read, Write и Seek.
type ReadWriter
type ReadWriter интерфейс {
Reader
Writer
}
ReadWriter — интерфейс, который группирует основные методы Read и Write.
type Reader
type Reader interface {
Read(p []byte) (n int, err error)
}
Reader — это интерфейс, который оборачивает базовый метод Read.
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. Следующий Read должен возвращать 0, EOF.
Вызывающие функции должны всегда обрабатывать возвращенные n > 0 байтов, прежде чем рассматривать ошибку err. Это позволяет правильно обрабатывать ошибки ввода-вывода, возникающие после чтения некоторых байтов, а также оба допустимых поведения EOF.
Если len(p) == 0, Read всегда должен возвращать n == 0. Он может возвращать ошибку, отличную от nil, если известно о каком-либо условии ошибки, таком как EOF.
Реализации Read не рекомендуется возвращать нулевое количество байтов с ошибкой nil, за исключением случаев, когда len(p) == 0. Вызывающие должны рассматривать возвращение 0 и nil как указание на то, что ничего не произошло; в частности, это не указывает на EOF.
Реализации не должны сохранять p.
func 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)
}
}
Output:
some
func 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)
}
}
Output:
first reader second reader third reader
func TeeReader
func TeeReader(r Reader, w Writer) Reader
TeeReader возвращает Reader, который записывает в w то, что он читает из r. Все чтения из r, выполняемые через него, сопоставляются с соответствующими записями в w. Внутренней буферизации нет — запись должна быть завершена до завершения чтения. Любая ошибка, возникшая во время записи, сообщается как ошибка чтения.
Пример
package main
import (
"io"
"log"
"os"
"strings"
)
func main() {
var r io.Reader = strings.NewReader("some io.Reader stream to be read\n")
r = io.TeeReader(r, os.Stdout)
// Everything read from r will be copied to stdout.
if _, err := io.ReadAll(r); err != nil {
log.Fatal(err)
}
}
Output:
some io.Reader stream to be read
type ReaderAt
type ReaderAt interface {
ReadAt(p []byte, off int64) (n int, err error)
}
ReaderAt — это интерфейс, который оборачивает базовый метод ReadAt.
ReadAt считывает len(p) байт в p, начиная с смещения off в базовом источнике ввода. Он возвращает количество прочитанных байтов (0 <= n <= len(p)) и любую возникшую ошибку.
Когда ReadAt возвращает n < len(p), он возвращает не нулевую ошибку, объясняющую, почему не было возвращено больше байтов. В этом отношении ReadAt более строг, чем Read.
Даже если ReadAt возвращает n < len(p), он может использовать все p в качестве временного пространства во время вызова. Если некоторые данные доступны, но не len(p) байтов, ReadAt блокируется до тех пор, пока не будут доступны все данные или не произойдет ошибка. В этом отношении ReadAt отличается от Read.
Если n = len(p) байтов, возвращаемых ReadAt, находятся в конце источника ввода, ReadAt может вернуть либо err == EOF, либо err == nil.
Если ReadAt читает из источника ввода с смещением поиска, ReadAt не должен влиять на базовое смещение поиска и не должен подвергаться его влиянию.
Клиенты ReadAt могут выполнять параллельные вызовы ReadAt на одном и том же источнике ввода.
Реализации не должны сохранять p.
Объяснение ReaderAt
Интерфейс ReaderAt в Go
Интерфейс ReaderAt определён в пакете io и позволяет читать данные из произвольного смещения (offset) без изменения состояния “читателя”.
Сигнатура метода
ReadAt(p []byte, off int64) (n int, err error)
p []byte— буфер, куда записываются прочитанные данные.off int64— смещение (в байтах) от начала источника данных.- Возвращает:
n— количество прочитанных байтов.err— ошибку (например,io.EOFпри достижении конца данных).
Пример использования ReaderAt
1. Чтение файла с произвольного смещения
package main
import (
"fmt"
"io"
"os"
)
func main() {
file, err := os.Open("example.txt")
if err != nil {
panic(err)
}
defer file.Close()
// Читаем 10 байт, начиная с 5-го байта
buf := make([]byte, 10)
n, err := file.ReadAt(buf, 5)
if err != nil && err != io.EOF {
panic(err)
}
fmt.Printf("Прочитано %d байт: %q\n", n, buf[:n])
}
Вывод (если example.txt содержит "Hello, world!"):
Прочитано 10 байт: ", world!"
2. Реализация собственного ReaderAt
Допустим, у нас есть структура, хранящая данные в памяти:
type MemoryReader struct {
data []byte
}
func (r *MemoryReader) ReadAt(p []byte, off int64) (n int, err error) {
if off >= int64(len(r.data)) {
return 0, io.EOF
}
n = copy(p, r.data[off:])
if n < len(p) {
err = io.EOF
}
return
}
func main() {
reader := &MemoryReader{data: []byte("RandomAccessData")}
buf := make([]byte, 5)
// Читаем 5 байт с 7-й позиции
n, err := reader.ReadAt(buf, 7)
if err != nil && err != io.EOF {
panic(err)
}
fmt.Printf("Прочитано: %q\n", buf[:n]) // "Access"
}
Назначение ReaderAt
- Произвольный доступ (random access) — чтение с любого места без последовательного перемещения.
- Потокобезопасность — метод
ReadAtможно вызывать из разных горутин (если реализация поддерживает). - Используется в:
- Файловых системах (
os.FileреализуетReaderAt). - Бинарных форматах (например, чтение заголовков архивов).
- Базах данных (чтение данных по смещению).
- Файловых системах (
Отличие от Reader
Reader (io.Reader) | ReaderAt (io.ReaderAt) |
|---|---|
Читает последовательно (Read меняет состояние). | Читает с любого места (ReadAt не меняет состояние). |
| Используется в потоковых данных (сети, pipes). | Используется для произвольного доступа (файлы, память). |
Пример: http.Response.Body. | Пример: os.File. |
Вывод
ReaderAt полезен, когда нужно читать данные с произвольных позиций, например, при работе с файлами или бинарными структурами. В отличие от Reader, он не зависит от текущей позиции и поддерживает конкурентный доступ.
type ReaderFrom
type ReaderFrom interface {
ReadFrom(r Reader) (n int64, err error)
}
ReaderFrom — это интерфейс, который оборачивает метод ReadFrom.
ReadFrom читает данные из r до EOF или ошибки. Возвращаемое значение n — это количество прочитанных байтов. Любая ошибка, кроме EOF, возникшая во время чтения, также возвращается.
Функция Copy использует ReaderFrom, если он доступен.
type RuneReader
type RuneReader interface {
ReadRune() (r rune, size int, err error)
}
RuneReader — это интерфейс, который оборачивает метод ReadRune.
ReadRune считывает один закодированный символ Unicode и возвращает руну и ее размер в байтах. Если символ недоступен, будет установлено err.
type RuneScanner
type RuneScanner interface {
RuneReader
UnreadRune() error
}
RuneScanner — это интерфейс, который добавляет метод UnreadRune к базовому методу ReadRune.
UnreadRune заставляет следующий вызов ReadRune возвращать последнюю прочитанную руну. Если последняя операция не была успешным вызовом ReadRune, UnreadRune может вернуть ошибку, не прочитать последнюю прочитанную руну (или руну, предшествующую последней непрочитанной руне), или (в реализациях, поддерживающих интерфейс Seeker) перейти к началу руны перед текущим смещением.
type SectionReader
type SectionReader struct {
// содержит отфильтрованные или неэкспортированные поля
}
SectionReader реализует Read, Seek и ReadAt на секции базового ReaderAt.
Объяснение SectionReader
Назначение SectionReader
SectionReader в Go (пакет io) позволяет читать только определённую часть (секцию) данных из базового источника, реализующего ReaderAt. Это полезно, когда нужно:
- Ограничить чтение определённым диапазоном байтов
- Работать с частью файла или данных как с самостоятельным потоком
- Читать данные с произвольных позиций (
ReadAt) и перемещаться по ним (Seek)
Пример использования всех методов
package main
import (
"fmt"
"io"
"strings"
)
func main() {
// Исходные данные
data := "Hello, World! This is a SectionReader example."
baseReader := strings.NewReader(data)
// Создаём SectionReader (база: data, смещение: 7, длина: 12 байтов)
section := io.NewSectionReader(baseReader, 7, 12)
// 1. Чтение всего раздела (Read)
buf := make([]byte, 12)
n, _ := section.Read(buf)
fmt.Printf("Read: %q (%d bytes)\n", buf[:n], n) // "World! This"
// 2. Чтение с позиции (ReadAt)
n, _ = section.ReadAt(buf, 2)
fmt.Printf("ReadAt(2): %q\n", buf[:n]) // "rld! This "
// 3. Перемещение (Seek)
section.Seek(5, io.SeekStart) // Перемещаемся на 5 байт от начала
n, _ = section.Read(buf)
fmt.Printf("After Seek(5): %q\n", buf[:n]) // "! This"
// 4. Получение параметров секции (Outer)
r, off, n := section.Outer()
fmt.Printf("Base: %T, Offset: %d, Size: %d\n", r, off, n)
// 5. Размер секции (Size)
fmt.Println("Section size:", section.Size()) // 12
}
Вывод:
Read: "World! This" (12 bytes)
ReadAt(2): "rld! This "
After Seek(5): "! This"
Base: *strings.Reader, Offset: 7, Size: 12
Section size: 12
Разбор методов
| Метод | Описание |
|---|---|
NewSectionReader() | Создаёт читатель для диапазона [off, off+n) из базового ReaderAt |
Read() | Читает данные последовательно (меняет текущую позицию) |
ReadAt() | Читает данные с указанного смещения (не меняет текущую позицию) |
Seek() | Перемещает текущую позицию (io.SeekStart/SeekCurrent/SeekEnd) |
Size() | Возвращает максимальный размер секции в байтах |
Outer() | Возвращает исходный ReaderAt, смещение и размер, переданные при создании |
Типичные сценарии использования
Чтение заголовков файлов
// Читаем первые 512 байт (например, заголовок ZIP) headerSection := io.NewSectionReader(file, 0, 512)Обработка частей больших файлов
// Читаем блок с 1024 по 2048 байт chunk := io.NewSectionReader(file, 1024, 1024)Виртуализация подмножества данных
// Работаем с частью данных как с самостоятельным io.Reader processData(section)
SectionReader особенно полезен при работе с бинарными форматами (архивы, медиафайлы), где нужно читать данные по чанкам или с определённых смещений.
func NewSectionReader
func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader
NewSectionReader возвращает SectionReader, который читает из r, начиная с смещения off, и останавливается с EOF после n байтов.
func (*SectionReader) Outer
func (s *SectionReader) Outer() (r ReaderAt, off int64, n int64)
Outer возвращает базовый ReaderAt и смещения для секции.
Возвращаемые значения совпадают с теми, которые были переданы в NewSectionReader при создании SectionReader.
func (*SectionReader) Read
func (s *SectionReader) Read(p []byte) (n int, err error)
Пример
import (
"fmt"
"io"
"log"
"strings"
)
func main() {
r := strings.NewReader("some io.Reader stream to be read\n")
s := io.NewSectionReader(r, 5, 17)
buf := make([]byte, 9)
if _, err := s.Read(buf); err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", buf)
}
Output:
io.Reader
func (*SectionReader) ReadAt
func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error)
Пример
import (
"fmt"
"io"
"log"
"strings"
)
func main() {
r := strings.NewReader("some io.Reader stream to be read\n")
s := io.NewSectionReader(r, 5, 17)
buf := make([]byte, 6)
if _, err := s.ReadAt(buf, 10); err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", buf)
}
Output:
stream
func (*SectionReader) Seek
func (s *SectionReader) Seek(offset int64, whence int) (int64, error)
Пример
package main
import (
"io"
"log"
"os"
"strings"
)
func main() {
r := strings.NewReader("some io.Reader stream to be read\n")
s := io.NewSectionReader(r, 5, 17)
if _, err := s.Seek(10, io.SeekStart); err != nil {
log.Fatal(err)
}
if _, err := io.Copy(os.Stdout, s); err != nil {
log.Fatal(err)
}
}
Output:
stream
func (*SectionReader) Size
func (s *SectionReader) Size() int64
Size возвращает размер секции в байтах.
Пример
package main
import (
"fmt"
"io"
"strings"
)
func main() {
r := strings.NewReader("some io.Reader stream to be read\n")
s := io.NewSectionReader(r, 5, 17)
fmt.Println(s.Size())
}
Output:
17
type Seeker
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
Seeker — это интерфейс, который оборачивает базовый метод Seek.
Seek устанавливает смещение для следующего Read или Write в offset, интерпретируемое в соответствии с whence: SeekStart означает относительно начала файла, SeekCurrent означает относительно текущего смещения, а SeekEnd означает относительно конца (например, offset = -2 указывает предпоследний байт файла). Seek возвращает новое смещение относительно начала файла или ошибку, если таковая имеется.
Переход к смещению перед началом файла является ошибкой. Переход к любому положительному смещению может быть разрешен, но если новое смещение превышает размер базового объекта, поведение последующих операций ввода-вывода зависит от реализации.
Объяснение Seeker
Назначение интерфейса Seeker в пакете io
Интерфейс Seeker определяет метод Seek(), который позволяет произвольно перемещаться по потоку данных (файлу, буферу в памяти, сетевому соединению и т.д.). Это ключевая возможность для работы с данными не только последовательно, но и в произвольном порядке.
Для чего нужен Seeker?
Произвольный доступ к данным
Возможность “перепрыгивать” к любому месту в потоке без чтения предыдущих данных.Чтение/запись в разные части файла
Например, обновление заголовка файла после записи основного содержимого.Реализация сложных протоколов
Парсинг структур, где нужно “заглядывать” вперед и возвращаться назад.Оптимизация работы с большими файлами
Чтение только нужных фрагментов без загрузки всего файла в память.
Детали метода Seek()
Seek(offset int64, whence int) (int64, error)
| Параметр | Значения (whence) | Описание |
|---|---|---|
offset | Любое число | Смещение в байтах (может быть отрицательным для SeekCurrent/SeekEnd) |
whence | io.SeekStart (0) | Отсчёт от начала данных |
io.SeekCurrent (1) | Отсчёт от текущей позиции | |
io.SeekEnd (2) | Отсчёт от конца данных |
Возвращает:
- Новую позицию (абсолютный offset от начала)
- Ошибку (например, попытка выйти за границы данных)
Примеры использования
1. Перемещение в файле
file, _ := os.Open("data.txt")
defer file.Close()
// Перемещаемся на 100-й байт от начала
pos, _ := file.Seek(100, io.SeekStart)
// Читаем 50 байт с этой позиции
buf := make([]byte, 50)
file.Read(buf)
2. Чтение с конца
// Перемещаемся на 10 байт назад от конца
pos, _ := file.Seek(-10, io.SeekEnd)
3. Относительное перемещение
// Текущая позиция: 200
// Перемещаемся на +50 байт вперёд
pos, _ := file.Seek(50, io.SeekCurrent) // Новый pos = 250
Где реализован Seeker?
Стандартные типы, поддерживающие интерфейс:
*os.File(файлы)*bytes.Reader(буфер в памяти)*strings.Reader(строка как поток)*SectionReader(часть другогоReaderAt)
Ограничения
Не все источники поддерживают
Например, сетевые соединения (net.Conn) не реализуютSeeker.Поведение зависит от типа
При работе с файлами на дискеSeekэффективен, но для сжатых данных (например, ZIP) может требовать декомпрессии.
Комбинация с другими интерфейсами
Часто используется вместе с:
Reader→io.ReadSeekerWriter→io.WriteSeekerReaderAt/WriterAtдля произвольного доступа без изменения позиции
Пример объединённого интерфейса:
type ReadSeeker interface {
Reader
Seeker
}
Итог
Seeker добавляет потокам данных критически важную возможность — произвольный доступ. Это фундамент для:
- Работы с бинарными форматами (архивы, медиафайлы)
- Оптимизированного чтения больших файлов
- Реализации сложных алгоритмов парсинга
- Многопроходной обработки данных без переоткрытия источника
type StringWriter
type StringWriter interface {
WriteString(s string) (n int, err error)
}
StringWriter — это интерфейс, который оборачивает метод WriteString.
type WriteCloser
type WriteCloser interface {
Writer
Closer
}
WriteCloser — это интерфейс, который группирует базовые методы Write и Close.
type WriteSeeker
type WriteSeeker interface {
Writer
Seeker
}
WriteSeeker — это интерфейс, который группирует базовые методы Write и Seek.
type Writer
type Writer interface {
Write(p []byte) (n int, err error)
}
Writer — это интерфейс, который оборачивает базовый метод Write.
Write записывает len(p) байт из p в базовый поток данных. Он возвращает количество байт, записанных из p (0 <= n <= len(p)), и любую ошибку, которая привела к преждевременному прекращению записи. Write должен возвращать ошибку, отличную от nil, если он возвращает n < len(p). Write не должен изменять данные слайса, даже временно.
Реализации не должны сохранять p.
var Discard Writer = discard{}
Discard — это Writer, на котором все вызовы Write выполняются успешно, не выполняя никаких действий.
Объяснение Writer
Интерфейс Writer в Go — это простой контракт для записи данных куда-либо. Он требует реализации всего одного метода:Write(p []byte) (n int, err error).
Как это работает?
Вы передаёте данные
Метод принимает байтовый слайс (p []byte), который нужно записать (например: текст, файл, сетевой пакет).Он пытается записать
Записывает часть или все данные изpв целевое место (файл, память, сеть и т.д.).Возвращает результат
n— сколько байт удалось записать (может быть меньше, чемlen(p)при ошибке).err— ошибка (например, диск заполнен, соединение разорвано).
Аналогия из жизни
Представьте, что Writer — это лестница в доме:
- Люди постепенно заходят на лестницу и поднимаются вверх (
p []byte). - На последнем этаже большой зал, люди заполняют его.
- Если зал заполнился полностью людьми, то часть останется на лестнице и в зал не войдут (
n < len(p)иerr != nil).
Где используется?
Примеры реализаций:
Запись в файл
file, _ := os.Create("log.txt") file.Write([]byte("Hello!")) // Реализует WriterОтправка данных по сети
conn, _ := net.Dial("tcp", "example.com:80") conn.Write([]byte("GET / HTTP/1.1\r\n\r\n"))Буфер в памяти
var buf bytes.Buffer buf.Write([]byte("Сохраняем в RAM"))Игнорирование данных (
Discard)io.Discard.Write([]byte("Эти данные никуда не пойдут"))
Правила для Write
Не изменяет
p
Даже временно. Ваши исходные данные останутся целыми.Не сохраняет
p
После завершения метода реализация не должна хранить ссылку на переданные данные.Ошибка = неполная запись
Если вернулосьn < len(p), метод обязан вернуть ошибку.
Особый случай: Discard
Это “пустышка”, которая реализует Writer, но просто выбрасывает все записываемые данные:
io.Discard.Write([]byte("Это исчезнет")) // Никуда не запишется
Зачем нужно? Например:
- Когда нужно прочитать данные, но не сохранять их.
- Для тестирования, чтобы имитировать запись без реальных операций.
Пример с кастомным Writer
Создадим простой Writer, который пишет данные в консоль:
type ConsoleWriter struct{}
func (cw ConsoleWriter) Write(p []byte) (int, error) {
n, err := fmt.Print(string(p))
return n, err
}
func main() {
var writer Writer = ConsoleWriter{}
writer.Write([]byte("Привет, Writer!"))
}
Вывод:
Привет, Writer!
func MultiWriter
func MultiWriter(writers ...Writer) Writer
MultiWriter создает писатель, который дублирует свои записи во всех предоставленных писателях, аналогично команде Unix tee(1).
Каждая запись записывается в каждый из перечисленных writer, по одному за раз. Если перечисленный writer возвращает ошибку, вся операция записи останавливается и возвращает ошибку; она не продолжается по списку.
Пример
package main
import (
"fmt"
"io"
"log"
"strings"
)
func main() {
r := strings.NewReader("some io.Reader stream to be read\n")
var buf1, buf2 strings.Builder
w := io.MultiWriter(&buf1, &buf2)
if _, err := io.Copy(w, r); err != nil {
log.Fatal(err)
}
fmt.Print(buf1.String())
fmt.Print(buf2.String())
}
Output:
some io.Reader stream to be read
some io.Reader stream to be read
type WriterAt
type WriterAt interface {
WriteAt(p []byte, off int64) (n int, err error)
}
WriterAt — это интерфейс, который оборачивает базовый метод WriteAt.
WriteAt записывает len(p) байт из p в базовый поток данных со смещением off. Он возвращает количество байт, записанных из p (0 <= n <= len(p)), и любую ошибку, которая привела к преждевременному прекращению записи. WriteAt должен возвращать ошибку, отличную от nil, если он возвращает n < len(p).
Если WriteAt записывает в место назначения со смещением поиска, WriteAt не должен влиять на базовое смещение поиска и не должен подвергаться его влиянию.
Клиенты WriteAt могут выполнять параллельные вызовы WriteAt на одном и том же месте назначения, если диапазоны не пересекаются.
Реализации не должны сохранять p.
type WriterTo
type WriterTo interface {
WriteTo(w Writer) (n int64, err error)
}
WriterTo — это интерфейс, который оборачивает метод WriteTo.
WriteTo записывает данные в w до тех пор, пока не закончатся данные для записи или не произойдет ошибка. Возвращаемое значение n — это количество записанных байтов. Любая ошибка, возникшая во время записи, также возвращается.
Функция Copy использует WriterTo, если он доступен.
Объяснение WriterTo
Интерфейс WriterTo — это продвинутая версия записи данных, которая позволяет объекту самому решать, как эффективно отправить свои данные в любой Writer (файл, сеть, буфер и т.д.).
Чем отличается от обычного Writer?
| Особенность | Writer (простой интерфейс) | WriterTo (продвинутый интерфейс) |
|---|---|---|
| Кто управляет? | Получатель (Writer) решает, как записать данные | Источник данных сам решает, как отправить данные |
| Эффективность | Может требовать промежуточных копирований | Позволяет оптимизировать запись (например, отправлять данные кусками) |
| Использование | Базовый уровень | Оптимизированные сценарии (например, io.Copy) |
Как работает метод WriteTo?
type WriterTo interface {
WriteTo(w Writer) (n int64, err error)
}
Вызывается у объекта-источника
Объект (например, буфер или файл) получает целевойWriter(w), куда нужно записать данные.Сам решает, как писать
Источник может:- Отправить данные одним куском
- Разбить на части
- Использовать специальные оптимизации (например, DMA для дисков)
Возвращает результат
n— сколько байт было записаноerr— ошибка (если что-то пошло не так)
Примеры использования
1. Стандартные типы с WriterTo
// bytes.Buffer реализует WriterTo
buf := bytes.NewBufferString("Hello!")
buf.WriteTo(os.Stdout) // Выведет "Hello!" в консоль
// strings.Reader тоже реализует WriterTo
reader := strings.NewReader("Text")
reader.WriteTo(file) // Запишет "Text" в файл
2. Кастомная реализация
Создадим объект, который знает, как эффективно записать свои данные:
type CustomData struct {
chunks [][]byte
}
func (d *CustomData) WriteTo(w io.Writer) (int64, error) {
var total int64
for _, chunk := range d.chunks {
n, err := w.Write(chunk)
total += int64(n)
if err != nil {
return total, err
}
}
return total, nil
}
// Использование:
data := CustomData{chunks: [][]byte{[]byte("Hello "), []byte("world!")}}
data.WriteTo(os.Stdout) // Выведет "Hello world!"
Зачем это нужно?
Оптимизация производительности
Объект может выбрать самый эффективный способ записи (например, избежать лишних копирований).Гибкость
Разные типы данных могут по-разному реализовывать запись:- Файл может отправлять данные кусками
- Буфер в памяти — одним вызовом
- Сетевое соединение — с контролем скорости
Интеграция с
io.Copy
Функцияio.Copy(dst, src)автоматически используетWriteTo, если он есть у источника:// Если src реализует WriterTo, Copy вызовет src.WriteTo(dst) io.Copy(file, buffer) // Будет эффективнее, чем обычное копирование
Особый случай: Discard
Как и с Writer, существует “пустышка”:
io.Discard.WriteTo(...) // Ничего не делает
Используется для тестирования или игнорирования данных.
9.3 - Пакет для работы с файловой системой io/fs
Для поддержки тестирования реализаций файловых систем смотрите пакет testing/fstest.
Переменные
var (
ErrInvalid = errInvalid() // "недопустимый аргумент"
ErrPermission = errPermission() // "разрешение отклонено"
ErrExist = errExist() // "файл уже существует"
ErrNotExist = errNotExist() // "файл не существует"
ErrClosed = errClosed() // "файл уже закрыт"
)
Общие ошибки файловой системы. Ошибки, возвращаемые файловыми системами, можно проверить на соответствие этим ошибкам с помощью errors.Is.
var SkipAll = errors.New("пропустить все и остановить прогулку")
SkipAll используется как возвращаемое значение из WalkDirFunc, чтобы указать, что все оставшиеся файлы и каталоги должны быть пропущены. Ни одна функция не возвращает его в качестве ошибки.
var SkipDir = errors.New("пропустить этот каталог")
SkipDir используется в качестве возвращаемого значения WalkDirFunc, чтобы указать, что каталог, названный в вызове, должен быть пропущен. Ни одна функция не возвращает его в качестве ошибки.
Функции
func FormatDirEntry
func FormatDirEntry(dir DirEntry) string
FormatDirEntry возвращает отформатированную версию dir для удобства чтения. Реализации DirEntry могут вызывать эту функцию из метода String. Результаты для каталога с именем subdir и файла с именем hello.go:
d subdir/
- hello.go
func FormatFileInfo
func FormatFileInfo(info FileInfo) string
FormatFileInfo возвращает отформатированную версию info для удобства чтения. Реализации FileInfo могут вызывать эту функцию из метода String. Результатом для файла с именем «hello.go», размером 100 байт, режимом 0o644, созданного 1 января 1970 года в полдень, будет
-rw-r--r-- 100 1970-01-01 12:00:00 hello.go
func Glob
func Glob(fsys FS, pattern string) (matches []string, err error)
Glob возвращает имена всех файлов, соответствующих pattern, или nil, если нет соответствующих файлов. Синтаксис шаблонов такой же, как в path.Match. Шаблон может описывать иерархические имена, такие как usr/*/bin/ed.
Glob игнорирует ошибки файловой системы, такие как ошибки ввода-вывода при чтении каталогов. Единственная возможная возвращаемая ошибка — path.ErrBadPattern, сообщающая о неверном формате шаблона.
Если fs реализует GlobFS, Glob вызывает fs.Glob. В противном случае Glob использует ReadDir для обхода дерева каталогов и поиска совпадений с шаблоном.
func ReadFile
func ReadFile(fsys FS, name string) ([]byte, error)
ReadFile читает файл с указанным именем из файловой системы fs и возвращает его содержимое. Успешный вызов возвращает ошибку nil, а не io.EOF. (Поскольку ReadFile читает весь файл, ожидаемый EOF от последнего Read не рассматривается как ошибка, о которой следует сообщать).
Если fs реализует ReadFileFS, ReadFile вызывает fs.ReadFile. В противном случае ReadFile вызывает fs.Open и использует Read и Close на возвращенном File.
func ValidPath
func ValidPath(name string) bool
ValidPath сообщает, является ли данное имя пути действительным для использования в вызове Open.
Имена путей, передаваемые в open, представляют собой закодированные в UTF-8, не имеющие корня, разделенные косой чертой последовательности элементов пути, например «x/y/z». Имена путей не должны содержать элемент «.» или «..» или пустую строку, за исключением особого случая, когда имя «.» может использоваться для корневого каталога. Пути не должны начинаться или заканчиваться косой чертой: «/x» и «x/» являются недопустимыми.
Обратите внимание, что пути разделяются косой чертой во всех системах, даже в Windows. Пути, содержащие другие символы, такие как обратная косая черта и двоеточие, принимаются как действительные, но эти символы никогда не должны интерпретироваться реализацией FS как разделители элементов пути.
func WalkDir
func WalkDir(fsys FS, root string, fn WalkDirFunc) error
WalkDir проходит по дереву файлов, корнем которого является root, вызывая fn для каждого файла или каталога в дереве, включая root.
Все ошибки, возникающие при посещении файлов и каталогов, фильтруются fn: подробности см. в документации fs.WalkDirFunc.
Файлы просматриваются в лексическом порядке, что делает вывод детерминированным, но требует, чтобы WalkDir считывал весь каталог в память, прежде чем приступать к просмотру этого каталога.
WalkDir не следует по символьным ссылкам, найденным в каталогах, но если root сам является символьной ссылкой, то будет пройдена его цель.
Пример
package main
import (
"fmt"
"io/fs"
"log"
"os"
"path/filepath"
"time"
)
func main() {
// Создаем временную файловую систему для демонстрации
tmpDir, err := os.MkdirTemp("", "fs-example-*")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(tmpDir)
// Создаем тестовые файлы и директории
createTestFS(tmpDir)
// Получаем файловую систему os.DirFS
fsys := os.DirFS(tmpDir)
// 1. Пример использования FormatDirEntry и FormatFileInfo
demoFormatFunctions(fsys)
// 2. Пример использования Glob
demoGlob(fsys)
// 3. Пример использования ReadFile
demoReadFile(fsys)
// 4. Пример использования ValidPath
demoValidPath()
// 5. Пример использования WalkDir
demoWalkDir(fsys)
}
func createTestFS(root string) {
// Создаем структуру:
// /tmp/fs-example-12345/
// ├── docs/
// │ ├── notes.md
// │ └── draft.txt
// ├── src/
// │ └── main.go
// └── README.txt
dirs := []string{
filepath.Join(root, "docs"),
filepath.Join(root, "src"),
}
files := map[string]string{
filepath.Join(root, "README.txt"): "Пример файла README",
filepath.Join(root, "docs", "notes.md"): "# Заметки\nПример содержимого",
filepath.Join(root, "docs", "draft.txt"): "Черновик документа",
filepath.Join(root, "src", "main.go"): "package main\n\nfunc main() {\n\tprintln(\"Hello\")\n}",
}
// Создаем директории
for _, dir := range dirs {
if err := os.MkdirAll(dir, 0755); err != nil {
log.Fatal(err)
}
}
// Создаем файлы
for path, content := range files {
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
log.Fatal(err)
}
}
// Устанавливаем время модификации для демонстрации FormatFileInfo
modTime := time.Date(2023, time.January, 15, 12, 30, 0, 0, time.UTC)
for path := range files {
if err := os.Chtimes(path, modTime, modTime); err != nil {
log.Fatal(err)
}
}
}
func demoFormatFunctions(fsys fs.FS) {
fmt.Println("\n=== FormatDirEntry и FormatFileInfo ===")
entries, err := fs.ReadDir(fsys, ".")
if err != nil {
log.Fatal(err)
}
for _, entry := range entries {
// FormatDirEntry
fmt.Printf("DirEntry: %s\n", fs.FormatDirEntry(entry))
// Получаем FileInfo для FormatFileInfo
info, err := entry.Info()
if err != nil {
log.Fatal(err)
}
fmt.Printf("FileInfo: %s\n", fs.FormatFileInfo(info))
}
}
func demoGlob(fsys fs.FS) {
fmt.Println("\n=== Glob ===")
// Ищем все .txt файлы
matches, err := fs.Glob(fsys, "*.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println("Файлы .txt в корне:", matches)
// Ищем все .go файлы в любых поддиректориях
matches, err = fs.Glob(fsys, "*/*.go")
if err != nil {
log.Fatal(err)
}
fmt.Println("Файлы .go в поддиректориях:", matches)
// Ищем все .md файлы рекурсивно
matches, err = fs.Glob(fsys, "**/*.md")
if err != nil {
log.Fatal(err)
}
fmt.Println("Файлы .md рекурсивно:", matches)
}
func demoReadFile(fsys fs.FS) {
fmt.Println("\n=== ReadFile ===")
// Читаем содержимое файла
content, err := fs.ReadFile(fsys, "src/main.go")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Содержимое main.go:\n%s\n", content)
// Попытка чтения несуществующего файла
_, err = fs.ReadFile(fsys, "nonexistent.txt")
fmt.Println("Ошибка при чтении несуществующего файла:", err)
}
func demoValidPath() {
fmt.Println("\n=== ValidPath ===")
paths := []string{
"valid/path",
"invalid/../path",
"invalid\\path", // Обратные слеши не считаются разделителями
"",
"trailing/slash/",
}
for _, path := range paths {
fmt.Printf("%q valid: %t\n", path, fs.ValidPath(path))
}
}
func demoWalkDir(fsys fs.FS) {
fmt.Println("\n=== WalkDir ===")
fmt.Println("Содержимое файловой системы:")
err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
fmt.Printf("- %s\n", fs.FormatDirEntry(d))
return nil
})
if err != nil {
log.Fatal(err)
}
}
=== FormatDirEntry и FormatFileInfo ===
DirEntry: d docs/
FileInfo: drwxr-xr-x 4096 2023-01-15 12:30:00 docs
DirEntry: d src/
FileInfo: drwxr-xr-x 4096 2023-01-15 12:30:00 src
DirEntry: - README.txt
FileInfo: -rw-r--r-- 19 2023-01-15 12:30:00 README.txt
=== Glob ===
Файлы .txt в корне: [README.txt]
Файлы .go в поддиректориях: [src/main.go]
Файлы .md рекурсивно: [docs/notes.md]
=== ReadFile ===
Содержимое main.go:
package main
func main() {
println("Hello")
}
Ошибка при чтении несуществующего файла: open nonexistent.txt: file does not exist
=== ValidPath ===
"valid/path" valid: true
"invalid/../path" valid: false
"invalid\\path" valid: true
"" valid: false
"trailing/slash/" valid: false
=== WalkDir ===
Содержимое файловой системы:
- .
- d docs/
- - docs/draft.txt
- - docs/notes.md
- d src/
- - src/main.go
- - README.txt
Типы
type DirEntry
type DirEntry interface {
// Name возвращает имя файла (или подкаталога), описанного записью.
// Это имя является только конечным элементом пути (базовым именем), а не всем путем.
// Например, Name вернет «hello.go», а не «home/gopher/hello.go».
Name() string
// IsDir сообщает, описывает ли запись каталог.
IsDir() bool
// Type возвращает биты типа для запис
// Биты типа являются подмножеством обычных битов FileMode, возвращаемых методом FileMode.Type.
Type() FileMode
// Info возвращает FileInfo для файла или подкаталога, описанного записью.
// Возвращаемая информация FileInfo может быть взята из времени первоначального чтения каталога
// или из времени вызова Info. Если файл был удален или переименован
// после чтения каталога, Info может вернуть ошибку, удовлетворяющую errors.Is(err, ErrNotExist).
// Если запись обозначает символическую ссылку, Info сообщает информацию о самой ссылке,
// а не о цели ссылки.
Info() (FileInfo, error)
}
DirEntry — это запись, прочитанная из каталога (с помощью функции ReadDir или метода ReadDir класса ReadDirFile).
func FileInfoToDirEntry
func FileInfoToDirEntry(info FileInfo) DirEntry
FileInfoToDirEntry возвращает DirEntry, который возвращает информацию из info. Если info равно nil, FileInfoToDirEntry возвращает nil.
func ReadDir
func ReadDir(fsys FS, name string) ([]DirEntry, error)
ReadDir считывает указанный каталог и возвращает список записей каталога, отсортированных по имени файла.
Если fs реализует ReadDirFS, ReadDir вызывает fs.ReadDir. В противном случае ReadDir вызывает fs.Open и использует ReadDir и Close для возвращенного файла.
type FS
type FS interface {
// Open открывает указанный файл.
// [File.Close] должен быть вызван для освобождения всех связанных ресурсов.
//
// Когда Open возвращает ошибку, она должна быть типа *PathError
// с полем Op, установленным в «open», полем Path, установленным в name,
// и полем Err, описывающим проблему.
//
// Open должен отклонять попытки открыть имена, которые не удовлетворяют
// ValidPath(name), возвращая *PathError с Err, установленным в
// ErrInvalid или ErrNotExist.
Open(name string) (File, error)
}
FS обеспечивает доступ к иерархической файловой системе.
Интерфейс FS является минимальной реализацией, необходимой для файловой системы. Файловая система может реализовывать дополнительные интерфейсы, такие как ReadFileFS, для предоставления дополнительных или оптимизированных функций.
testing/fstest.TestFS может использоваться для проверки правильности реализации FS.
Объяснение FS
Объяснение интерфейса fs.FS
fs.FS - это базовый интерфейс в Go для работы с иерархическими файловыми системами. Он представляет минимальный контракт, который должна реализовать любая файловая система (реальная, виртуальная, в памяти и т.д.).
Ключевые особенности:
- Минималистичный дизайн - только один обязательный метод
Open() - Абстракция - позволяет работать с разными ФС одинаковым способом
- Расширяемость - через дополнительные интерфейсы (
ReadFileFS,GlobFSи др.)
Основной метод:
Open(name string) (File, error)
- Открывает файл по имени
- Возвращает объект, реализующий
fs.File - В случае ошибки возвращает
*fs.PathError
Пример реализации и использования
1. Создаем простую in-memory файловую систему
package main
import (
"io/fs"
"log"
"os"
"time"
)
// MemoryFS - простая in-memory реализация fs.FS
type MemoryFS struct {
files map[string]*MemoryFile
}
type MemoryFile struct {
name string
content []byte
mode fs.FileMode
modTime time.Time
}
func (m *MemoryFile) Stat() (fs.FileInfo, error) {
return &MemoryFileInfo{m}, nil
}
func (m *MemoryFile) Read(p []byte) (int, error) {
// Реализация чтения
}
func (m *MemoryFile) Close() error {
return nil
}
// MemoryFileInfo реализует fs.FileInfo
type MemoryFileInfo struct {
file *MemoryFile
}
func (m *MemoryFileInfo) Name() string { return m.file.name }
func (m *MemoryFileInfo) Size() int64 { return int64(len(m.file.content)) }
func (m *MemoryFileInfo) Mode() fs.FileMode { return m.file.mode }
func (m *MemoryFileInfo) ModTime() time.Time { return m.file.modTime }
func (m *MemoryFileInfo) IsDir() bool { return m.file.mode.IsDir() }
func (m *MemoryFileInfo) Sys() interface{} { return nil }
// Open реализует fs.FS
func (mfs *MemoryFS) Open(name string) (fs.File, error) {
if !fs.ValidPath(name) {
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: fs.ErrInvalid,
}
}
file, exists := mfs.files[name]
if !exists {
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: fs.ErrNotExist,
}
}
return file, nil
}
2. Используем нашу реализацию
func main() {
// Инициализируем нашу ФС
mfs := &MemoryFS{
files: map[string]*MemoryFile{
"hello.txt": {
name: "hello.txt",
content: []byte("Hello, MemoryFS!"),
mode: 0644,
modTime: time.Now(),
},
"dir": {
name: "dir",
mode: fs.ModeDir | 0755,
modTime: time.Now(),
},
},
}
// Пример 1: Открываем и читаем файл
file, err := mfs.Open("hello.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
info, _ := file.Stat()
fmt.Printf("Файл: %s, размер: %d\n", info.Name(), info.Size())
// Пример 2: Пытаемся открыть несуществующий файл
_, err = mfs.Open("missing.txt")
if err != nil {
fmt.Printf("Ошибка: %v\n", err) // Выведет PathError
}
// Пример 3: Используем с другими функциями пакета fs
content, err := fs.ReadFile(mfs, "hello.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Содержимое: %s\n", content)
}
3. Пример вывода:
Файл: hello.txt, размер: 15
Ошибка: open missing.txt: file does not exist
Содержимое: Hello, MemoryFS!
Где используется fs.FS?
В стандартной библиотеке:
os.DirFS- доступ к реальной файловой системеembed.FS- доступ к встроенным файламhttp.FileSystem- интеграция с веб-сервером
В популярных библиотеках:
- Виртуальные файловые системы
- Работа с архивами (zip, tar)
- Тестирование (testing/fstest)
В пользовательских реализациях:
- ФС в памяти
- ФС поверх облачного хранилища
- ФС для специализированных форматов
Преимущества такого подхода:
- Единообразие - один интерфейс для разных ФС
- Тестируемость - легко подменять реальную ФС на mock
- Гибкость - можно комбинировать разные реализации
Интерфейс fs.FS стал стандартным способом работы с файловыми системами в Go 1.16+, заменив множество специализированных решений.
func Sub
func Sub(fsys FS, dir string) (FS, error)
Sub возвращает FS, соответствующую поддереву с корнем в fsys’s dir.
Если dir равна «.», Sub возвращает fsys без изменений. В противном случае, если fs реализует SubFS, Sub возвращает fsys.Sub(dir). В противном случае Sub возвращает новую реализацию FS sub, которая фактически реализует sub.Open(name) как fsys.Open(path.Join(dir, name)). Реализация также соответствующим образом преобразует вызовы ReadDir, ReadFile и Glob.
Обратите внимание, что Sub(os.DirFS(«/»), „prefix“) эквивалентно os.DirFS(«/prefix») и что ни одно из них не гарантирует отсутствие доступа операционной системы за пределами «/prefix», поскольку реализация os.DirFS не проверяет символьные ссылки внутри «/prefix», которые указывают на другие каталоги. То есть os.DirFS не является общей заменой механизма безопасности типа chroot, и Sub не меняет этот факт.
type File
type File interface {
Stat() (FileInfo, error)
Read([]byte) (int, error)
Close() error
}
File предоставляет доступ к одному файлу. Интерфейс File является минимальной реализацией, требуемой для файла. Файлы каталогов также должны реализовывать ReadDirFile. Файл может реализовывать io.ReaderAt или io.Seeker в качестве оптимизаций.
type FileInfo
type FileInfo interface {
Name() string // базовое имя файла
Size() int64 // длина в байтах для обычных файлов; зависит от системы для других
Mode() FileMode // биты режима файла
ModTime() time.Time // время изменения
IsDir() bool // сокращение для Mode().IsDir()
Sys() any // базовый источник данных (может возвращать nil)
}
FileInfo описывает файл и возвращается Stat.
func Stat
func Stat(fsys FS, name string) (FileInfo, error)
Stat возвращает FileInfo, описывающий файл с указанным именем из файловой системы.
Если fs реализует StatFS, Stat вызывает fs.Stat. В противном случае Stat открывает файл для его статистики.
type FileMode
type FileMode uint32
FileMode представляет режим файла и биты разрешений. Биты имеют одинаковое определение во всех системах, поэтому информация о файлах может быть перенесена из одной системы в другую. Не все биты применимы ко всем системам. Единственный обязательный бит — ModeDir для каталогов.
const (
// Одиночные буквы являются аббревиатурами,
// используемыми для форматирования методом String.
ModeDir FileMode = 1 << (32 - 1 - iota) // d: является каталогом
ModeAppend // a: только для добавления
ModeExclusive // l: исключительное использование
ModeTemporary // T: временный файл; только Plan 9
ModeSymlink // L: символическая ссылка
ModeDevice // D: файл устройства
ModeNamedPipe // p: именованный канал (FIFO)
ModeSocket // S: сокет домена Unix
ModeSetuid // u: setuid
ModeSetgid // g: setgid
ModeCharDevice // c: символьное устройство Unix, когда установлен ModeDevice
ModeSticky // t: sticky
ModeIrregular // ?: нерегулярный файл; ничего больше не известно об этом файле
// Маска для битов типа. Для обычных файлов не будет установлено ничего.
ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular
ModePerm FileMode = 0777 // биты разрешений Unix)
Определенные биты режима файла являются наиболее значимыми битами FileMode. Девять наименее значимых битов являются стандартными разрешениями Unix rwxrwxrwx. Значения этих битов следует рассматривать как часть общедоступного API и могут использоваться в протоколах передачи данных или представлениях диска: они не должны изменяться, хотя могут быть добавлены новые биты.
func (FileMode) IsDir
func (m FileMode) IsDir() bool
IsDir сообщает, описывает ли m каталог. То есть, он проверяет, установлен ли бит ModeDir в m.
func (FileMode) IsRegular
func (m FileMode) IsRegular() bool
IsRegular сообщает, описывает ли m обычный файл. То есть, он проверяет, что биты типа режима не установлены.
func (FileMode) Perm
func (m FileMode) Perm() FileMode
Perm возвращает биты разрешений Unix в m (m & ModePerm).
func (FileMode) String
func (m FileMode) String() string
func (FileMode) Type
func (m FileMode) Type() FileMode
Type возвращает биты типа в m (m & ModeType).
type GlobFS
type GlobFS interface {
FS
// Glob возвращает имена всех файлов, соответствующих шаблону,
// предоставляя реализацию функции верхнего уровня
// Glob.
Glob(pattern string) ([]string, error)
}
GlobFS — это файловая система с методом Glob.
type PathError
type PathError struct {
Op string
Path string
Err error
}
PathError регистрирует ошибку, а также операцию и путь к файлу, которые ее вызвали.
func (*PathError) Error
func (e *PathError) Error() string
func (*PathError) Timeout
func (e *PathError) Timeout() bool
Timeout сообщает, является ли эта ошибка тайм-аутом.
func (*PathError) Unwrap
func (e *PathError) Unwrap() error
type ReadDirFS
type ReadDirFS interface {
FS
// ReadDir читает указанный каталог
// и возвращает список записей каталога, отсортированных по имени файла.
ReadDir(name string) ([]DirEntry, error)
}
ReadDirFS — это интерфейс, реализованный файловой системой, который обеспечивает оптимизированную реализацию ReadDir.
type ReadDirFile
type ReadDirFile interface {
file
// ReadDir считывает содержимое каталога и возвращает
// массив из n значений DirEntry в порядке каталога.
// Последующие вызовы для того же файла будут возвращать дальнейшие значения DirEntry.
//
// Если n > 0, ReadDir возвращает не более n структур DirEntry.
// В этом случае, если ReadDir возвращает пустой срез, он вернет
// не нулевую ошибку с объяснением причины.
// В конце каталога ошибкой будет io.EOF.
// (ReadDir должен возвращать io.EOF сам, а не ошибку, оборачивающую io.EOF.)
//
// Если n <= 0, ReadDir возвращает все значения DirEntry из каталога
// в одном срезе. В этом случае, если ReadDir завершается успешно (считывает все
// до конца каталога), он возвращает срез и ошибку nil.
// Если он встречает ошибку до конца каталога,
// ReadDir возвращает список DirEntry, прочитанный до этого момента, и ошибку, отличную от nil.
ReadDir(n int) ([]DirEntry, error)
}
ReadDirFile — это файл каталога, записи которого можно прочитать с помощью метода ReadDir. Каждый файл каталога должен реализовывать этот интерфейс. (Любой файл может реализовывать этот интерфейс, но в этом случае ReadDir должен возвращать ошибку для некаталогов.)
type ReadFileFS
type ReadFileFS interface {
FS
// ReadFile читает указанный файл и возвращает его содержимое.
// Успешный вызов возвращает ошибку nil, а не io.EOF.
// (Поскольку ReadFile считывает весь файл, ожидаемый EOF
// от последнего Read не рассматривается как ошибка, о которой следует сообщать.)
//
// Вызывающему разрешается изменять возвращаемый байтовый срез.
// Этот метод должен возвращать копию базовых данных.
ReadFile(name string) ([]byte, error)
}
ReadFileFS — это интерфейс, реализованный файловой системой, который обеспечивает оптимизированную реализацию ReadFile.
type StatFS
type StatFS interface {
FS
// Stat возвращает FileInfo, описывающий файл.
// Если происходит ошибка, она должна быть типа *PathError.
Stat(name string) (FileInfo, error)
}
StatFS — это файловая система с методом Stat.
type SubFS
type SubFS interface {
FS
// Sub возвращает FS, соответствующий поддереву с корнем в dir.
Sub(dir string) (FS, error)
}
SubFS — это файловая система с методом Sub.
type WalkDirFunc
type WalkDirFunc func(path string, d DirEntry, err error) error
WalkDirFunc — это тип функции, вызываемой WalkDir для посещения каждого файла или каталога.
Аргумент path содержит аргумент WalkDir в качестве префикса. То есть, если WalkDir вызывается с корневым аргументом «dir» и находит файл с именем «a» в этом каталоге, функция walk будет вызвана с аргументом «dir/a».
Аргумент d — это DirEntry для указанного пути.
Результат error, возвращаемый функцией, контролирует продолжение работы WalkDir. Если функция возвращает специальное значение SkipDir, WalkDir пропускает текущий каталог (path, если d.IsDir() равно true, в противном случае — родительский каталог path). Если функция возвращает специальное значение SkipAll, WalkDir пропускает все оставшиеся файлы и каталоги. В противном случае, если функция возвращает ошибку, отличную от nil, WalkDir полностью останавливается и возвращает эту ошибку.
Аргумент err сообщает об ошибке, связанной с путем, сигнализируя, что WalkDir не будет проходить в этот каталог. Функция может решить, как обработать эту ошибку; как описано ранее, возврат ошибки приведет к тому, что WalkDir прекратит прохождение всего дерева.
WalkDir вызывает функцию с аргументом err, отличным от nil, в двух случаях.
Во-первых, если первоначальная Stat в корневом каталоге завершается с ошибкой, WalkDir вызывает функцию с path, установленным в root, d, установленным в nil, и err, установленным в ошибку из fs.Stat.
Во-вторых, если метод ReadDir каталога (см. ReadDirFile) завершается с ошибкой, WalkDir вызывает функцию с path, установленным в путь каталога, d, установленным в DirEntry, описывающий каталог, и err, установленным в ошибку из ReadDir. Во втором случае функция вызывается дважды с путем к каталогу: первый вызов происходит до попытки чтения каталога, и err установлен в nil, что дает функции возможность вернуть SkipDir или SkipAll и полностью избежать ReadDir. Второй вызов происходит после неудачного ReadDir и сообщает об ошибке из ReadDir. (Если ReadDir завершается успешно, второго вызова не происходит.)
Различия между WalkDirFunc и path/filepath.WalkFunc заключаются в следующем:
Второй аргумент имеет тип DirEntry вместо FileInfo. Функция вызывается перед чтением каталога, чтобы SkipDir или SkipAll могли полностью обойти чтение каталога или пропустить все оставшиеся файлы и каталоги соответственно. Если чтение каталога завершилось неудачно, функция вызывается второй раз для этого каталога, чтобы сообщить об ошибке.
10 - Пакет Sync для Go
За исключением типов Once и WaitGroup, большинство из них предназначены для использования низкоуровневыми библиотечными процедурами. Синхронизацию более высокого уровня лучше выполнять через каналы и коммуникации.
Значения, содержащие типы, определенные в этом пакете, не должны копироваться.
Функции
func OnceFunc
func OnceFunc(f func()) func()
OnceFunc возвращает функцию, которая вызывает f только один раз. Возвращаемая функция может вызываться одновременно.
Если f вызывает панику, возвращаемая функция будет вызывать панику с тем же значением при каждом вызове.
func OnceValue
func OnceValue[T any](f func() T) func() T
OnceValue возвращает функцию, которая вызывает f только один раз и возвращает значение, возвращаемое f. Возвращаемая функция может вызываться одновременно.
Если f вызывает панику, возвращаемая функция будет вызывать панику с тем же значением при каждом вызове.
Пример
В этом примере OnceValue используется для выполнения «дорогостоящего» вычисления только один раз, даже при одновременном использовании.
package main
import (
"fmt"
"sync"
)
func main() {
once := sync.OnceValue(func() int {
sum := 0
for i := 0; i < 1000; i++ {
sum += i
}
fmt.Println("Computed once:", sum)
return sum
})
done := make(chan bool)
for i := 0; i < 10; i++ {
go func() {
const want = 499500
got := once()
if got != want {
fmt.Println("want", want, "got", got)
}
done <- true
}()
}
for i := 0; i < 10; i++ {
<-done
}
}
Output:
Computed once: 499500
func OnceValues
func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2)
OnceValues возвращает функцию, которая вызывает f только один раз и возвращает значения, возвращаемые f. Возвращаемая функция может вызываться одновременно.
Если f вызывает панику, возвращаемая функция будет вызывать панику с тем же значением при каждом вызове.
Пример
В этом примере используется OnceValues для однократного чтения файла.
package main
import (
"fmt"
"os"
"sync"
)
func main() {
once := sync.OnceValues(func() ([]byte, error) {
fmt.Println("Reading file once")
return os.ReadFile("example_test.go")
})
done := make(chan bool)
for i := 0; i < 10; i++ {
go func() {
data, err := once()
if err != nil {
fmt.Println("error:", err)
}
_ = data // Ignore the data for this example
done <- true
}()
}
for i := 0; i < 10; i++ {
<-done
}
}
Типы
type Cond
type Cond struct {
// L удерживается во время наблюдения или изменения условия
L Locker
// содержит отфильтрованные или неэкспортируемые поля
}
Cond реализует переменную условия, точку встречи для goroutines, ожидающих или объявляющих о наступлении события.
Каждый Cond имеет связанный с ним Locker L (часто *Mutex или *RWMutex), который должен удерживаться при изменении условия и при вызове метода Cond.Wait.
Cond не должен копироваться после первого использования.
В терминологии модели памяти Go, Cond организует так, что вызов Cond.Broadcast или Cond.Signal «синхронизируется перед» любым вызовом Wait, который он разблокирует.
Для многих простых случаев использования пользователям будет удобнее использовать каналы, чем Cond (Broadcast соответствует закрытию канала, а Signal — отправке по каналу).
Для получения дополнительной информации о заменах sync.Cond см. серию статей Роберто Клаписа о расширенных моделях параллелизма, а также доклад Брайана Миллса о моделях параллелизма.
func NewCond
func NewCond(l Locker) *Cond
NewCond возвращает новый Cond с Locker l.
func (*Cond) Broadcast
func (c *Cond) Broadcast()
Broadcast пробуждает все goroutines, ожидающие c.
Вызывающему разрешается, но не требуется, удерживать c.L во время вызова.
func (*Cond) Signal
func (c *Cond) Signal()
Signal пробуждает одну goroutine, ожидающую c, если таковая имеется.
Вызывающему разрешается, но не требуется, удерживать c.L во время вызова.
Signal() не влияет на приоритет планирования goroutine; если другие goroutines пытаются заблокировать c.L, они могут быть пробуждены раньше «ожидающей» goroutine.
func (*Cond) Wait
func (c *Cond) Wait()
Wait атомарно разблокирует c.L и приостанавливает выполнение вызывающего goroutine. После возобновления выполнения Wait блокирует c.L перед возвратом. В отличие от других систем, Wait не может вернуться, если его не разбудит Cond.Broadcast или Cond.Signal.
Поскольку c.L не блокируется во время ожидания Wait, вызывающий обычно не может предполагать, что условие будет выполнено, когда Wait вернется. Вместо этого вызывающий должен ждать в цикле:
c.L.Lock()
for !condition() {
c.Wait()
}...
использовать условие ...
c.L.Unlock()
Подробное объяснение типа Cond
Что такое Cond?
Cond (от слова “condition” - условие) - это примитив синхронизации из пакета sync, который позволяет горутинам ожидать или объявлять о наступлении некоторого события.
Проще говоря, Cond нужен для того, чтобы:
- Одни горутины могли “уснуть” и ждать какого-то условия
- Другие горутины могли их “разбудить”, когда это условие выполнится
Основные методы:
Wait()- блокирует горутину до получения уведомленияSignal()- пробуждает одну случайную горутину из ожидающихBroadcast()- пробуждает все ожидающие горутины
Зачем это нужно?
Cond особенно полезен в ситуациях, где:
- Несколько горутин ожидают какого-то общего условия
- Состояние может измениться в любой момент
- Вы не хотите постоянно опрашивать условие в цикле (busy waiting)
Реальный пример: Ограниченная очередь
Допустим, у нас есть очередь с ограниченным размером, и мы хотим:
- Блокировать писателей, когда очередь полна
- Блокировать читателей, когда очередь пуста
package main
import (
"fmt"
"sync"
"time"
)
type BoundedQueue struct {
mu sync.Mutex
cond *sync.Cond
queue []int
size int
cap int
}
func NewBoundedQueue(capacity int) *BoundedQueue {
q := &BoundedQueue{
queue: make([]int, 0, capacity),
cap: capacity,
}
q.cond = sync.NewCond(&q.mu)
return q
}
func (q *BoundedQueue) Put(item int) {
q.mu.Lock()
defer q.mu.Unlock()
// Ждем, пока освободится место
for q.size == q.cap {
q.cond.Wait()
}
q.queue = append(q.queue, item)
q.size++
fmt.Printf("Добавлен элемент %d. Размер очереди: %d\n", item, q.size)
// Уведомляем ожидающих читателей
q.cond.Broadcast()
}
func (q *BoundedQueue) Get() int {
q.mu.Lock()
defer q.mu.Unlock()
// Ждем, пока появится элемент
for q.size == 0 {
q.cond.Wait()
}
item := q.queue[0]
q.queue = q.queue[1:]
q.size--
fmt.Printf("Извлечен элемент %d. Размер очереди: %d\n", item, q.size)
// Уведомляем ожидающих писателей
q.cond.Broadcast()
return item
}
func main() {
queue := NewBoundedQueue(3)
// Писатели
for i := 0; i < 5; i++ {
go func(val int) {
queue.Put(val)
}(i)
}
// Читатели
for i := 0; i < 5; i++ {
go func() {
time.Sleep(1 * time.Second)
queue.Get()
}()
}
time.Sleep(5 * time.Second)
}
Когда использовать Cond вместо каналов?
Cond полезен, когда:
- У вас сложное условие ожидания (не просто “есть данные”)
- Нужно уведомлять сразу несколько горутин
- Состояние может меняться часто и нужно минимизировать накладные расходы
Каналы лучше подходят для более простых случаев передачи данных между горутинами.
sync.Cond - это инструмент для сложных сценариев синхронизации, где горутинам нужно ждать выполнения определенных условий. Он особенно полезен при реализации структур данных с ограничениями (как в нашем примере с очередью), пулов ресурсов или других сценариев, где состояние может меняться и нужно эффективно уведомлять ожидающие горутины.
type Locker
type Locker интерфейс {
Lock()
Unlock()
}
Locker - это интерфейс из пакета sync, который определяет базовые методы для блокировки:
Подробное объяснение типа Locker
Этот интерфейс реализуют:
sync.Mutex- обычная мьютекс-блокировкаsync.RWMutex- блокировка с возможностью множественного чтения- Любые другие типы, которые реализуют эти два метода
Зачем нужен Locker?
- Унификация работы с разными типами блокировок - вы можете писать функции, которые работают с любым типом блокировки
- Абстракция - позволяет не зависеть от конкретной реализации блокировки
- Тестирование - можно создавать mock-объекты для тестирования
Пример 1: Использование с sync.Mutex
package main
import (
"fmt"
"sync"
"time"
)
func increment(counter *int, locker sync.Locker) {
locker.Lock()
defer locker.Unlock()
*counter++
fmt.Println(*counter)
}
func main() {
var counter int
var mu sync.Mutex
for i := 0; i < 5; i++ {
go increment(&counter, &mu)
}
time.Sleep(1 * time.Second)
}
Пример 2: Собственная реализация Locker
type DebugLocker struct {
mu sync.Mutex
}
func (d *DebugLocker) Lock() {
fmt.Println("Lock acquired")
d.mu.Lock()
}
func (d *DebugLocker) Unlock() {
fmt.Println("Lock released")
d.mu.Unlock()
}
func main() {
var counter int
locker := &DebugLocker{}
for i := 0; i < 5; i++ {
go increment(&counter, locker)
}
time.Sleep(1 * time.Second)
}
Реальный пример использования
Допустим, у нас есть кэш, который может использовать разные виды блокировок:
type Cache struct {
locker sync.Locker
data map[string]string
}
func NewCache(locker sync.Locker) *Cache {
return &Cache{
locker: locker,
data: make(map[string]string),
}
}
func (c *Cache) Set(key, value string) {
c.locker.Lock()
defer c.locker.Unlock()
c.data[key] = value
}
func (c *Cache) Get(key string) (string, bool) {
c.locker.Lock()
defer c.locker.Unlock()
val, ok := c.data[key]
return val, ok
}
func main() {
// Можно использовать обычный Mutex
cache1 := NewCache(&sync.Mutex{})
// Или RWMutex для оптимизации чтения
cache2 := NewCache(&sync.RWMutex{})
// Или даже нашу DebugLocker
cache3 := NewCache(&DebugLocker{})
}Locker представляет объект, который можно заблокировать и разблокировать.
Когда использовать Locker?
- Когда ваша функция/метод должен работать с разными типами блокировок
- Когда вы хотите сделать код более гибким для тестирования
- Когда вы разрабатываете библиотеку и хотите оставить выбор блокировки пользователю
Locker - это простой интерфейс, который позволяет абстрагироваться от конкретного типа блокировки. Он делает ваш код более гибким и переиспользуемым, особенно когда речь идет о конкурентных операциях.
type Map
type Map struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Map похож на Go map[any]any, но безопасен для одновременного использования несколькими goroutines без дополнительной блокировки или координации. Загрузка, хранение и удаление выполняются за амортизированное постоянное время.
Тип Map является специализированным. В большинстве случаев следует использовать обычный Go map с отдельной блокировкой или координацией, чтобы обеспечить лучшую типовую безопасность и упростить поддержание других инвариантов наряду с содержимым карты.
Тип Map оптимизирован для двух распространенных случаев использования:
- когда запись для данного ключа записывается только один раз, но читается много раз, как в кэшах, которые только растут, или
- когда несколько goroutines читают, записывают и перезаписывают записи для непересекающихся наборов ключей.
В этих двух случаях использование Map может значительно уменьшить конфликты блокировок по сравнению с Go map в паре с отдельным Mutex или RWMutex.
Карта с нулевым значением пуста и готова к использованию. Карту нельзя копировать после первого использования.
В терминологии модели памяти Go карта организует так, что операция записи «синхронизируется перед» любой операцией чтения, которая наблюдает эффект записи, где операции чтения и записи определяются следующим образом.
- Map.Load, Map.LoadAndDelete, Map.LoadOrStore, Map.Swap, Map.CompareAndSwap и Map.CompareAndDelete — операции чтения;
- Map.Delete, Map.LoadAndDelete, Map.Store и Map.Swap — операции записи;
- Map.LoadOrStore — операция записи, когда она возвращает загруженный набор, установленный в false;
- Map.CompareAndSwap — операция записи, когда она возвращает помененный набор, установленный в true; и
- Map.CompareAndDelete — операция записи, когда она возвращает удаленный набор, установленный в true.
func (*Map) Clear
func (m *Map) Clear()
Clear удаляет все записи, в результате чего карта становится пустой.
func (*Map) CompareAndDelete
func (m *Map) CompareAndDelete(key, old any) (deleted bool)
CompareAndDelete удаляет запись для ключа, если его значение равно old. Старое значение должно быть сопоставимого типа.
Если в карте нет текущего значения для ключа, CompareAndDelete возвращает false (даже если старое значение является значением интерфейса nil).
Подробное объяснение типа Map и функции CompareAndDelete
Что делает CompareAndDelete?
Метод CompareAndDelete выполняет атомарную операцию:
- Проверяет, соответствует ли текущее значение для указанного ключа
oldзначению - Если значения совпадают - удаляет запись из мапы
- Возвращает
true, если удаление произошло, иfalseв противном случае
Это операция “compare-and-delete” (сравнить и удалить), аналогичная атомарным операциям CAS (Compare-And-Swap).
Как это работает?
deleted := m.CompareAndDelete(key, oldValue)
- Если в мапе
keyотсутствует → возвращаетfalse - Если в мапе
keyесть, но значение ≠oldValue→ возвращаетfalse - Если в мапе
keyесть и значение ==oldValue→ удаляет запись и возвращаетtrue
Пример использования
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
// Добавляем значения в мапу
m.Store("counter", 42)
m.Store("flag", true)
m.Store("name", "Alice")
// Попытка удалить с неправильным старым значением
deleted := m.CompareAndDelete("counter", 100)
fmt.Printf("Удаление counter=100: %v\n", deleted) // false
// Удаление с правильным значением
deleted = m.CompareAndDelete("counter", 42)
fmt.Printf("Удаление counter=42: %v\n", deleted) // true
// Проверяем, что counter удален
_, ok := m.Load("counter")
fmt.Printf("counter существует: %v\n", ok) // false
// Попытка удалить несуществующий ключ
deleted = m.CompareAndDelete("nonexistent", nil)
fmt.Printf("Удаление nonexistent: %v\n", deleted) // false
// Пример с nil значением
m.Store("nil-value", nil)
deleted = m.CompareAndDelete("nil-value", nil)
fmt.Printf("Удаление nil-value: %v\n", deleted) // true
}
Реальный кейс использования
Представим систему, где несколько горутин обновляют статус задач:
type TaskStatus string
const (
Pending TaskStatus = "pending"
Running TaskStatus = "running"
Completed TaskStatus = "completed"
)
func completeTask(m *sync.Map, taskID string) bool {
// Пытаемся перевести задачу из running в completed
return m.CompareAndDelete(taskID, Running)
}
func main() {
var tasks sync.Map
// Инициализируем задачи
tasks.Store("task1", Pending)
tasks.Store("task2", Running)
tasks.Store("task3", Running)
// Горутины пытаются завершить задачи
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if completeTask(&tasks, "task2") {
fmt.Println("Задача task2 завершена")
}
}()
}
wg.Wait()
// Проверяем оставшиеся задачи
tasks.Range(func(key, value interface{}) bool {
fmt.Printf("%s: %s\n", key, value)
return true
})
}
Особенности работы
- Безопасность для concurrent-использования: метод можно вызывать из нескольких горутин без дополнительной синхронизации
- Сравнение значений: сравнение происходит через
==, поэтому для сложных типов нужно быть внимательным - nil значения: даже если
old- nil, метод вернетfalseесли ключа нет в мапе
Когда использовать?
CompareAndDelete полезен в сценариях:
- Удаление устаревших данных (когда значение соответствует ожидаемому)
- Реализация конечных автоматов (state machines)
- Оптимистичные блокировки (optimistic locking)
- Удаление элементов только при определенных условиях
Этот метод особенно полезен в конкурентных сценариях, где нужно атомарно проверить и удалить значение без явных блокировок.
func (*Map) CompareAndSwap
func (m *Map) CompareAndSwap(key, old, new any) (swapped bool)
CompareAndSwap меняет местами старое и новое значения для ключа, если значение, хранящееся в карте, равно old. Старое значение должно быть сравнимого типа.
func (*Map) Delete
func (m *Map) Delete(key any)
Delete удаляет значение для ключа.
func (*Map) Load
func (m *Map) Load(key any) (value any, ok bool)
Load возвращает значение, хранящееся в карте для ключа, или nil, если значение отсутствует. Результат ok указывает, было ли найдено значение в карте.
func (*Map) LoadAndDelete
func (m *Map) LoadAndDelete(key any) (value any, loaded bool)
LoadAndDelete удаляет значение для ключа, возвращая предыдущее значение, если оно есть. Результат loaded сообщает, присутствовал ли ключ.
func (*Map) LoadOrStore
func (m *Map) LoadOrStore(key, value any) (actual any, loaded bool)
Метод LoadOrStore выполняет атомарную операцию “загрузить или сохранить” и особенно полезен в конкурентных сценариях, когда несколько горутин могут одновременно пытаться работать с одними и теми же ключами.
Объяснеие, что происходит на самом деле
Как это работает?
actual, loaded := m.LoadOrStore(key, value)
Если ключ
keyуже существует в мапе:- Возвращает существующее значение (в
actual) loaded = true(значение было загружено)
- Возвращает существующее значение (в
Если ключ
keyне существует в мапе:- Сохраняет переданное
valueпо этому ключу - Возвращает это же значение (в
actual) loaded = false(значение было сохранено)
- Сохраняет переданное
Простой пример
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
// Пытаемся сохранить значение "apple" по ключу "fruit"
// Так как ключа нет - оно будет сохранено
actual, loaded := m.LoadOrStore("fruit", "apple")
fmt.Printf("Ключ 'fruit': actual=%v, loaded=%v\n", actual, loaded)
// Вывод: Ключ 'fruit': actual=apple, loaded=false
// Пытаемся сохранить "banana" по тому же ключу
// Но ключ уже существует - возвращается текущее значение
actual, loaded = m.LoadOrStore("fruit", "banana")
fmt.Printf("Ключ 'fruit': actual=%v, loaded=%v\n", actual, loaded)
// Вывод: Ключ 'fruit': actual=apple, loaded=true
// Проверяем текущее значение
value, _ := m.Load("fruit")
fmt.Println("Текущее значение для 'fruit':", value)
// Вывод: Текущее значение для 'fruit': apple
}
Реальный пример: кэширование результатов
Представим сервис, который кэширует результаты дорогих вычислений:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
var cache sync.Map
func expensiveCalculation(id int) int {
// Имитация долгого вычисления
time.Sleep(time.Second)
return rand.Intn(1000)
}
func getCachedResult(id int) int {
// Пытаемся получить результат из кэша
result, loaded := cache.LoadOrStore(id, expensiveCalculation(id))
if loaded {
fmt.Printf("Результат для %d взят из кэша\n", id)
} else {
fmt.Printf("Результат для %d вычислен и сохранен\n", id)
}
return result.(int)
}
func main() {
rand.Seed(time.Now().UnixNano())
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
getCachedResult(id % 3) // Используем только 3 разных ID
}(i)
}
wg.Wait()
}
Возможный вывод:
Результат для 0 вычислен и сохранен
Результат для 1 вычислен и сохранен
Результат для 2 вычислен и сохранен
Результат для 0 взят из кэша
Результат для 1 взят из кэша
Особенности LoadOrStore
- Атомарность: Операция выполняется атомарно, что делает её безопасной для использования из нескольких горутин
- Эффективность: Избегает “гонки” при инициализации значений
- Удобство: Заменяет распространённый паттерн “проверить-затем-сохранить”
Когда использовать?
LoadOrStore идеально подходит для:
- Кэширования результатов
- Инициализации синглтонов
- Создания элементов по требованию
- Любых сценариев, где нужно “получить или создать” значение атомарно
Этот метод особенно полезен в высоконагруженных системах, где несколько горутин могут одновременно запрашивать одни и те же ресурсы.
func (*Map) Range
func (m *Map) Range(f func(key, value any) bool)
Range вызывает f последовательно для каждого ключа и значения, присутствующих в карте. Если f возвращает false, range останавливает итерацию.
Range не обязательно соответствует какому-либо последовательному снимку содержимого карты: ни один ключ не будет посещен более одного раза, но если значение для любого ключа хранится или удаляется одновременно (в том числе f), Range может отражать любое сопоставление для этого ключа из любой точки во время вызова Range. Range не блокирует другие методы на приемнике; даже f может вызывать любой метод на m.
Range может быть O(N) с количеством элементов в карте, даже если f возвращает false после постоянного числа вызовов.
func (*Map) Store
func (m *Map) Store(key, value any)
Store устанавливает значение для ключа.
func (*Map) Swap
func (m *Map) Swap(key, value any) (previous any, loaded bool)
Swap заменяет значение ключа и возвращает предыдущее значение, если оно есть. Результат loaded сообщает, присутствовал ли ключ.
type Mutex
type Mutex struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Mutex — это блокировка взаимного исключения. Нулевое значение для Mutex — это разблокированный мьютекс.
Mutex не должен копироваться после первого использования.
В терминологии модели памяти Go n-й вызов Mutex.Unlock «синхронизирует перед» m-й вызов Mutex.Lock для любого n < m. Успешный вызов Mutex.TryLock эквивалентен вызову Lock. Неудачный вызов TryLock не устанавливает никаких отношений «синхронизирует перед».
Объяснение Mutex
Mutex (сокращение от “mutual exclusion” - взаимное исключение) - это примитив синхронизации, который позволяет только одной горутине за раз получать доступ к общему ресурсу.
Представьте его как дверь в туалет:
- Когда кто-то внутри, дверь заперта (Lock)
- Другие ждут снаружи (блокируются)
- Когда человек выходит, он открывает дверь (Unlock)
- Тогда следующий может войти
Основные методы:
Lock()- захватывает мьютекс (блокирует, если он уже захвачен)Unlock()- освобождает мьютексTryLock()- пытается захватить мьютекс без блокировки (возвращает успех/неудачу)
Простой пример:
package main
import (
"fmt"
"sync"
"time"
)
var counter int
var mu sync.Mutex // Создаем мьютекс
func increment() {
mu.Lock() // Захватываем мьютекс
defer mu.Unlock() // Гарантируем освобождение
temp := counter
time.Sleep(1 * time.Millisecond) // Имитируем работу
counter = temp + 1
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait()
fmt.Println("Итоговое значение счетчика:", counter) // Всегда 100
}
Реальный пример: Банковский перевод
type BankAccount struct {
balance int
mu sync.Mutex
}
func (acc *BankAccount) Deposit(amount int) {
acc.mu.Lock()
defer acc.mu.Unlock()
acc.balance += amount
}
func (acc *BankAccount) Withdraw(amount int) bool {
acc.mu.Lock()
defer acc.mu.Unlock()
if acc.balance < amount {
return false
}
acc.balance -= amount
return true
}
func (acc *BankAccount) Transfer(to *BankAccount, amount int) bool {
// Важно: блокируем оба счета в одном порядке, чтобы избежать deadlock
acc.mu.Lock()
defer acc.mu.Unlock()
to.mu.Lock()
defer to.mu.Unlock()
if acc.balance < amount {
return false
}
acc.balance -= amount
to.balance += amount
return true
}
Когда использовать Mutex?
- Когда несколько горутин работают с общими данными
- Когда нужно гарантировать целостность данных
- Для защиты операций, которые должны выполняться атомарно
Важные правила:
- Всегда освобождайте мьютекс (лучше через
defer) - Не копируйте мьютекс после использования
- Избегайте блокировок на долгое время
- Соблюдайте порядок блокировки нескольких мьютексов
TryLock пример:
func tryUpdate(data *string) {
var mu sync.Mutex
if mu.TryLock() {
defer mu.Unlock()
*data = "updated"
} else {
fmt.Println("Не удалось получить блокировку")
}
}
func (*Mutex) Lock
func (m *Mutex) Lock()
Lock блокирует m. Если блокировка уже используется, вызывающая goroutine блокируется до тех пор, пока мьютекс не станет доступным.
func (*Mutex) TryLock
func (m *Mutex) TryLock() bool
TryLock пытается заблокировать m и сообщает, удалось ли это.
Обратите внимание, что хотя правильные способы использования TryLock существуют, они редки, и использование TryLock часто является признаком более глубокой проблемы в конкретном использовании мьютексов.
func (*Mutex) Unlock
func (m *Mutex) Unlock()
Unlock разблокирует m. Если m не заблокирован при входе в Unlock, возникает ошибка выполнения.
Заблокированный мьютекс не связан с конкретной горутиной. Одна горутина может заблокировать мьютекс, а затем организовать его разблокировку другой горутиной.
type Once
type Once struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Once — это объект, который выполнит ровно одно действие.
Once нельзя копировать после первого использования.
В терминологии модели памяти Go возврат из f «синхронизируется перед» возвратом из любого вызова once.Do(f).
Пример
package main
import (
"fmt"
"sync"
)
func main() {
var once sync.Once
onceBody := func() {
fmt.Println("Only once")
}
done := make(chan bool)
for i := 0; i < 10; i++ {
go func() {
once.Do(onceBody)
done <- true
}()
}
for i := 0; i < 10; i++ {
<-done
}
}
Этот код демонстрирует использование sync.Once в Go - структуры, которая гарантирует, что определённая функция будет выполнена ровно один раз, даже если её вызов происходит из нескольких горутин.
Разберём код по частям:
- Инициализация:
var once sync.Once
onceBody := func() {
fmt.Println("Only once")
}
- Создаётся объект
sync.Once - Определяется функция
onceBody, которая будет выполнена один раз
- Канал для синхронизации:
done := make(chan bool)
- Создаётся буферизированный канал для ожидания завершения всех горутин
- Запуск горутин:
for i := 0; i < 10; i++ {
go func() {
once.Do(onceBody)
done <- true
}()
}
- Запускается 10 горутин
- Каждая вызывает
once.Do(onceBody) - После выполнения отправляет сигнал в канал
done
- Ожидание завершения:
for i := 0; i < 10; i++ {
<-done
}
- Основная горутина ждёт 10 сигналов (по одному от каждой горутины)
Что произойдёт при выполнении:
- Все 10 горутин попытаются выполнить
onceBodyчерезonce.Do() sync.Onceгарантирует, что:- Функция
onceBodyбудет выполнена только один раз - Остальные горутин будут ждать завершения этого выполнения
- Все последующие вызовы будут проигнорированы
- Функция
- В результате в консоли мы увидим только одно сообщение “Only once”
Практическое применение:
- Инициализация глобальных ресурсов
- Ленивая инициализация
- Создание синглтонов
- Однократное выполнение настройки
Этот пример наглядно показывает мощь sync.Once для решения задач, требующих однократного выполнения в конкурентной среде.
func (*Once) Do
func (o *Once) Do(f func())
Do вызывает функцию f, если и только если Do вызывается впервые для этого экземпляра Once. Другими словами, при условии
var once Once
если once.Do(f) вызывается несколько раз, только первый вызов вызовет f, даже если f имеет разное значение при каждом вызове. Для выполнения каждой функции требуется новый экземпляр Once.
Do предназначен для инициализации, которая должна выполняться ровно один раз. Поскольку f является нулевым, может потребоваться использовать функциональный литерал для захвата аргументов функции, которая будет вызвана Do:
config.once.Do(func() { config.init(filename) })
Поскольку ни один вызов Do не возвращается до тех пор, пока не вернется один вызов f, если f вызывает Do, это приведет к тупиковой ситуации.
Если f вызывает панику, Do считает, что она вернулась; будущие вызовы Do возвращаются без вызова f.
type Pool
type Pool struct {
// New опционально указывает функцию для генерации
// значения, когда Get в противном случае вернул бы nil.
// Он не может быть изменен одновременно с вызовами Get.
New func() any
// содержит отфильтрованные или неэкспортируемые поля
}
Pool — это набор временных объектов, которые могут быть индивидуально сохранены и извлечены.
Любой элемент, хранящийся в Pool, может быть удален автоматически в любое время без уведомления. Если Pool содержит единственную ссылку, когда это происходит, элемент может быть деаллоцирован.
Pool безопасен для одновременного использования несколькими goroutines.
Цель пула — кэшировать выделенные, но неиспользуемые элементы для повторного использования в будущем, снимая нагрузку с сборщика мусора. То есть он упрощает создание эффективных, потокобезопасных списков свободных элементов. Однако он подходит не для всех списков свободных элементов.
Пул целесообразно использовать для управления группой временных элементов, которые незаметно совместно используются и потенциально повторно используются одновременно независимыми клиентами пакета. Пул позволяет амортизировать накладные расходы на выделение памяти между многими клиентами.
Примером правильного использования пула является пакет fmt, который поддерживает хранилище временных буферов вывода динамического размера. Хранилище масштабируется под нагрузкой (когда многие goroutines активно печатают) и сокращается в состоянии покоя.
С другой стороны, свободный список, поддерживаемый как часть короткоживущего объекта, не подходит для использования в пуле, поскольку в этом сценарии накладные расходы не амортизируются должным образом. Более эффективно, чтобы такие объекты реализовывали свой собственный свободный список.
Пул не должен копироваться после первого использования.
В терминологии модели памяти Go вызов Put(x) «синхронизируется перед» вызовом Pool.Get, возвращающим то же значение x. Аналогично, вызов New, возвращающий x, «синхронизируется перед» вызовом Get, возвращающим то же значение x.
Пример
package main
import (
"bytes"
"io"
"os"
"sync"
"time"
)
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
func timeNow() time.Time {
return time.Unix(1136214245, 0)
}
func Log(w io.Writer, key, val string) {
b := bufPool.Get().(*bytes.Buffer)
b.Reset()
b.WriteString(timeNow().UTC().Format(time.RFC3339))
b.WriteByte(' ')
b.WriteString(key)
b.WriteByte('=')
b.WriteString(val)
w.Write(b.Bytes())
bufPool.Put(b)
}
func main() {
Log(os.Stdout, "path", "/search?q=flowers")
}
Этот пример демонстрирует эффективное использование sync.Pool для оптимизации работы с временными объектами (в данном случае - bytes.Buffer).
1. Пул буферов (sync.Pool)
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
- Создается пул объектов
bytes.Buffer - Функция
Newсоздает новый буфер, когда пул пуст - Важно: возвращаются указатели (
new(bytes.Buffer)), чтобы избежать лишних аллокаций
2. Функция логирования
func Log(w io.Writer, key, val string) {
// 1. Получаем буфер из пула (или создаем новый)
b := bufPool.Get().(*bytes.Buffer)
// 2. Сбрасываем состояние буфера перед использованием
b.Reset()
// 3. Формируем строку лога
b.WriteString(timeNow().UTC().Format(time.RFC3339))
b.WriteByte(' ')
b.WriteString(key)
b.WriteByte('=')
b.WriteString(val)
// 4. Выводим лог
w.Write(b.Bytes())
// 5. Возвращаем буфер в пул для повторного использования
bufPool.Put(b)
}
3. Тестовая реализация времени
func timeNow() time.Time {
return time.Unix(1136214245, 0) // Фиксированное время для демонстрации
}
Как это работает:
При первом вызове
Log:- Пул пуст, поэтому вызывается
Newи создается новыйbytes.Buffer - Буфер используется и возвращается в пул
- Пул пуст, поэтому вызывается
При последующих вызовах:
- Буфер берется из пула (без аллокации)
- Сбрасывается (
Reset()) - Используется повторно
Преимущества подхода:
Снижение нагрузки на GC:
- Буферы переиспользуются, а не создаются заново
- Меньше работы для сборщика мусора
Экономия памяти:
- Не нужно постоянно выделять/освобождать память
- Размер пула автоматически регулируется
Потокобезопасность:
sync.Poolбезопасен для использования из нескольких горутин
Важные нюансы:
Обязательно сбрасывайте состояние:
- Перед использованием вызовите
Reset(), чтобы очистить предыдущие данные
- Перед использованием вызовите
Не сохраняйте объекты из пула:
- После
Put()считайте объект более недоступным
- После
Подходит для часто создаваемых объектов:
- Идеально для объектов, которые:
- Дорого создавать
- Используются кратковременно
- Имеют примерно одинаковый размер
- Идеально для объектов, которые:
Реальный вывод программы:
2006-01-02T15:04:05Z path=/search?q=flowers
Этот паттерн особенно полезен в:
- Логгерах
- HTTP middleware
- Любом коде, где часто создаются временные буферы
func (*Pool) Get
func (p *Pool) Get() any
Get выбирает произвольный элемент из пула, удаляет его из пула и возвращает вызывающему. Get может игнорировать пул и рассматривать его как пустой. Вызывающие не должны предполагать какую-либо связь между значениями, переданными в Pool.Put, и значениями, возвращаемыми Get.
Если Get в противном случае вернул бы nil, а p.New не равен nil, Get возвращает результат вызова p.New.
func (*Pool) Put
func (p *Pool) Put(x any)
Put добавляет x в пул.
type RWMutex
type RWMutex struct {
// содержит отфильтрованные или неэкспортируемые поля
}
RWMutex — это блокировка взаимного исключения для чтения/записи. Блокировка может удерживаться произвольным количеством читателей или одним записывающим устройством. Нулевое значение для RWMutex — это разблокированный мьютекс.
RWMutex не должен копироваться после первого использования.
Если какой-либо goroutine вызывает RWMutex.Lock, когда блокировка уже удерживается одним или несколькими читателями, одновременные вызовы RWMutex.RLock будут блокироваться до тех пор, пока записывающий не получит (и не освободит) блокировку, чтобы обеспечить доступность блокировки для записывающего. Обратите внимание, что это запрещает рекурсивную блокировку чтения. RWMutex.RLock не может быть повышен до RWMutex.Lock, а RWMutex.Lock не может быть понижен до RWMutex.RLock.
В терминологии модели памяти Go n-й вызов RWMutex.Unlock «синхронизируется перед» m-м вызовом Lock для любого n < m, так же как и для Mutex. Для любого вызова RLock существует n, такое что n-й вызов Unlock «синхронизируется перед» этим вызовом RLock, а соответствующий вызов RWMutex.RUnlock «синхронизируется перед» n+1-м вызовом Lock.
func (*RWMutex) Lock
func (rw *RWMutex) Lock()
Lock блокирует rw для записи. Если блокировка уже заблокирована для чтения или записи, Lock блокируется до тех пор, пока блокировка не станет доступной.
func (*RWMutex) RLock
func (rw *RWMutex) RLock()
RLock блокирует rw для чтения.
Его не следует использовать для рекурсивной блокировки чтения; заблокированный вызов Lock исключает возможность получения блокировки новыми читателями. См. документацию по типу RWMutex.
func (*RWMutex) RLocker
func (rw *RWMutex) RLocker() Locker
RLocker возвращает интерфейс Locker, который реализует методы [Locker.Lock] и [Locker.Unlock] путем вызова rw.RLock и rw.RUnlock.
func (*RWMutex) RUnlock
func (rw *RWMutex) RUnlock()
RUnlock отменяет один вызов RWMutex.RLock; это не влияет на других одновременных читателей. Если rw не заблокирован для чтения при входе в RUnlock, возникает ошибка выполнения.
func (*RWMutex) TryLock
func (rw *RWMutex) TryLock() bool
TryLock пытается заблокировать rw для записи и сообщает, удалось ли это.
Обратите внимание, что хотя правильное использование TryLock существует, оно встречается редко, и использование TryLock часто является признаком более глубокой проблемы в конкретном использовании мьютексов.
func (*RWMutex) TryRLock
func (rw *RWMutex) TryRLock() bool
TryRLock пытается заблокировать rw для чтения и сообщает, удалось ли это.
Обратите внимание, что хотя правильное использование TryRLock существует, оно встречается редко, и использование TryRLock часто является признаком более глубокой проблемы в конкретном использовании мьютексов.
func (*RWMutex) Unlock
func (rw *RWMutex) Unlock()
Unlock разблокирует rw для записи. Если rw не заблокирован для записи при входе в Unlock, возникает ошибка выполнения.
Как и в случае с мьютексами, заблокированный RWMutex не связан с конкретной горутиной. Одна горутина может RWMutex.RLock (RWMutex.Lock) RWMutex, а затем организовать, чтобы другая горутина RWMutex.RUnlock (RWMutex.Unlock) его.
type WaitGroup
type WaitGroup struct {
// содержит отфильтрованные или неэкспортируемые поля
}
WaitGroup ожидает завершения работы набора goroutines. Основной goroutine вызывает WaitGroup.Add, чтобы установить количество goroutines, которые необходимо дождаться. Затем каждый из goroutines запускается и вызывает WaitGroup.Done по завершении работы. В то же время WaitGroup.Wait можно использовать для блокировки до тех пор, пока все goroutines не завершат работу.
WaitGroup нельзя копировать после первого использования.
В терминологии модели памяти Go вызов WaitGroup.Done «синхронизирует перед» возвратом любого вызова Wait, который он разблокирует.
Пример
package main
import (
"sync"
)
type httpPkg struct{}
func (httpPkg) Get(url string) {}
var http httpPkg
func main() {
var wg sync.WaitGroup
var urls = []string{
"http://www.golang.org/",
"http://www.google.com/",
"http://www.example.com/",
}
for _, url := range urls {
// Increment the WaitGroup counter.
wg.Add(1)
// Launch a goroutine to fetch the URL.
go func(url string) {
// Decrement the counter when the goroutine completes.
defer wg.Done()
// Fetch the URL.
http.Get(url)
}(url)
}
// Wait for all HTTP fetches to complete.
wg.Wait()
}
Этот пример демонстрирует классический паттерн использования sync.WaitGroup для ожидания завершения группы горутин.
1. Структура и имитация HTTP-клиента
type httpPkg struct{}
func (httpPkg) Get(url string) {} // Пустой метод для демонстрации
var http httpPkg // Глобальная переменная "HTTP-клиента"
- Создается пустая структура
httpPkgс методомGet(имитация HTTP-запроса) - Создается глобальная переменная
httpэтого типа
2. Основная функция
func main() {
var wg sync.WaitGroup // Создаем WaitGroup
urls := []string{ // Список URL для обработки
"http://www.golang.org/",
"http://www.google.com/",
"http://www.example.com/",
}
- Инициализируется
WaitGroupдля отслеживания горутин - Определяется список URL для обработки
3. Запуск горутин
for _, url := range urls {
wg.Add(1) // Увеличиваем счетчик WaitGroup перед запуском горутины
go func(url string) { // Запускаем горутину для каждого URL
defer wg.Done() // Уменьшаем счетчик при завершении (defer гарантирует выполнение)
http.Get(url) // "Выполняем" HTTP-запрос
}(url) // Важно: передаем url как параметр, чтобы избежать проблем с замыканием
}
- Для каждого URL:
wg.Add(1)- увеличиваем счетчик ожидаемых горутин- Запускаем анонимную функцию как горутину
defer wg.Done()- гарантируем уменьшение счетчика при любом выходе из функции- Выполняем “запрос” с помощью
http.Get
4. Ожидание завершения
wg.Wait() // Блокируемся, пока счетчик не станет 0
}
- Основная горутина блокируется, пока все запущенные горутины не вызовут
Done()
Как это работает:
- Изначально счетчик WaitGroup = 0
- Для каждого URL:
Add(1)увеличивает счетчик (становится 1, 2, 3…)- Горутина запускается
- Каждая горутина при завершении вызывает
Done()(уменьшает счетчик) Wait()блокируется, пока счетчик не вернется к 0
Важные моменты:
Порядок операций:
- Всегда вызывайте
Add()ДО запуска горутины - Используйте
defer wg.Done()для надежности
- Всегда вызывайте
Передача параметров:
- URL передается как аргумент в горутину (
go func(url string)) - Это избегает проблем с общим доступом к переменной цикла
- URL передается как аргумент в горутину (
Реальные применения:
- Параллельные HTTP-запросы
- Обработка файлов/данных в нескольких горутинах
- Любые задачи, которые можно распараллелить
Что произойдет при выполнении:
- Запустятся 3 горутины (по одной на каждый URL)
- Основная горутина заблокируется на
wg.Wait() - Когда все 3 горутины завершатся (вызовут
Done()) - Основная горутина продолжит выполнение (и завершит программу)
Этот паттерн - основа для многих конкурентных операций в Go, где нужно дождаться завершения группы горутин.
func (*WaitGroup) Add
func (wg *WaitGroup) Add(delta int)
Add добавляет delta, которая может быть отрицательной, к счетчику WaitGroup. Если счетчик становится равным нулю, все goroutines, заблокированные WaitGroup.Wait, освобождаются. Если счетчик становится отрицательным, Add вызывает панику.
Обратите внимание, что вызовы с положительной дельтой, которые происходят, когда счетчик равен нулю, должны происходить перед Wait. Вызовы с отрицательной дельтой или вызовы с положительной дельтой, которые начинаются, когда счетчик больше нуля, могут происходить в любое время. Обычно это означает, что вызовы Add должны выполняться до оператора, создающего goroutine или другое событие, которое необходимо ожидать. Если WaitGroup повторно используется для ожидания нескольких независимых наборов событий, новые вызовы Add должны происходить после того, как все предыдущие вызовы Wait вернулись. См. пример WaitGroup.
func (*WaitGroup) Done
func (wg *WaitGroup) Done()
Done уменьшает счетчик WaitGroup на единицу.
func (*WaitGroup) Wait
func (wg *WaitGroup) Wait()
Wait блокируется до тех пор, пока счетчик WaitGroup не станет равным нулю.
10.1 - Описание пакета sync/atomic
Для правильного использования этих функций требуется большая осторожность. За исключением специальных низкоуровневых приложений, синхронизацию лучше выполнять с помощью каналов или средств пакета sync. Делитесь памятью, общаясь; не общайтесь, делясь памятью.
Объяснение atomic
Низкоуровневые примитивы атомарной памяти в Go
Атомарные операции - это операции, которые выполняются полностью (как единое целое) без возможности прерывания их другими потоками. В Go они предоставляются пакетом
sync/atomicи используются для безопасной работы с разделяемой памятью в многопоточных программах.
Основные понятия
- Атомарность - операция либо выполняется полностью, либо не выполняется вообще
- Гонка данных (data race) - ситуация, когда несколько горутин одновременно обращаются к одной переменной, и хотя бы одна из них выполняет запись
- Примитивы - базовые строительные блоки для синхронизации
Основные типы и функции
Пакет sync/atomic работает с:
int32,int64,uint32,uint64,uintptr- Указателями (через
unsafe.Pointer)
Примеры использования
1. Атомарное увеличение счетчика
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var counter int64
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
atomic.AddInt64(&counter, 1)
wg.Done()
}()
}
wg.Wait()
fmt.Println("Counter:", counter) // Гарантированно выведет 100
}
2. Атомарное чтение и запись
package main
import (
"fmt"
"sync/atomic"
"time"
)
func main() {
var value int32
// Горутина, которая изменяет значение
go func() {
time.Sleep(time.Millisecond * 100)
atomic.StoreInt32(&value, 42)
}()
// Главная горутина ждет изменения значения
for {
if atomic.LoadInt32(&value) == 42 {
fmt.Println("Value has been updated to 42")
break
}
time.Sleep(time.Millisecond * 10)
}
}
3. Сравнение и замена (CAS - Compare And Swap)
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var value int32 = 10
// Пытаемся заменить 10 на 20
swapped := atomic.CompareAndSwapInt32(&value, 10, 20)
fmt.Println("Swapped:", swapped, "Value:", value) // Swapped: true Value: 20
// Пытаемся заменить 10 на 30 (но текущее значение уже 20)
swapped = atomic.CompareAndSwapInt32(&value, 10, 30)
fmt.Println("Swapped:", swapped, "Value:", value) // Swapped: false Value: 20
}
4. Атомарный указатель
package main
import (
"fmt"
"sync/atomic"
"unsafe"
)
type Data struct {
value int
}
func main() {
var ptr unsafe.Pointer
// Создаем данные
data1 := &Data{value: 42}
data2 := &Data{value: 100}
// Атомарно сохраняем указатель
atomic.StorePointer(&ptr, unsafe.Pointer(data1))
// Атомарно читаем указатель
current := (*Data)(atomic.LoadPointer(&ptr))
fmt.Println("Current value:", current.value) // 42
// Атомарно меняем указатель
atomic.StorePointer(&ptr, unsafe.Pointer(data2))
current = (*Data)(atomic.LoadPointer(&ptr))
fmt.Println("New value:", current.value) // 100
}
Когда использовать atomic
- Когда нужны простые операции над разделяемыми переменными
- Когда производительность критична (atomic обычно быстрее мьютексов)
- Для реализации более сложных примитивов синхронизации
Когда НЕ использовать atomic
- Когда логика сложная - лучше использовать мьютексы или каналы
- Когда нужно работать со сложными структурами данных
- Когда важна читаемость кода (atomic код может быть сложнее для понимания)
Атомарные операции - это низкоуровневый механизм, который требует аккуратного использования, но может быть очень эффективным в правильных сценариях.
Операция обмена, реализуемая функциями SwapT, является атомарным эквивалентом:
old = *addr
*addr = new
return old
Операция сравнения и замены, реализуемая функциями CompareAndSwapT, является атомарным эквивалентом:
if *addr == old {
*addr = new
return true
}
return false
Операция сложения, реализуемая функциями AddT, является атомарным эквивалентом:
*addr += delta
return *addr
Операции load и store, реализуемые функциями LoadT и StoreT, являются атомарными эквивалентами “return *addr” и “*addr = val”.
В терминологии модели памяти Go, если эффект атомарной операции A наблюдается атомарной операцией B, то A “синхронизируется раньше” B. Кроме того, все атомарные операции, выполняемые в программе, ведут себя так, как будто выполняются в некотором последовательно согласованном порядке. Это определение обеспечивает ту же семантику, что и последовательные атомики в C++ и волатильные переменные в Java.
Функции
func AddInt32
func AddInt32(addr *int32, delta int32) (new int32)
AddInt32 атомарно добавляет delta к *addr и возвращает новое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Add.
func AddInt64
func AddInt64(addr *int64, delta int64) (new int64)
AddInt64 атомарно добавляет delta к *addr и возвращает новое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Add (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func AddUint32
func AddUint32(addr *uint32, delta uint32) (new uint32)
AddUint32 атомарно добавляет дельту к *addr и возвращает новое значение. Чтобы вычесть из x знаковое положительное константное значение c, выполните AddUint32(&x, ^uint32(c-1)). В частности, чтобы уменьшить x, выполните AddUint32(&x, ^uint32(0)). Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Add.
func AddUint64
func AddUint64(addr *uint64, delta uint64) (new uint64)
AddUint64 атомарно добавляет delta к *addr и возвращает новое значение. Чтобы вычесть из x знаковое положительное константное значение c, выполните AddUint64(&x, ^uint64(c-1)). В частности, чтобы уменьшить x, выполните AddUint64(&x, ^uint64(0)). Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Add (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func AddUintptr
func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)
AddUintptr атомарно добавляет delta к *addr и возвращает новое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Add.
func AndInt32
func AndInt32(addr *int32, mask int32) (old int32)
AndInt32 атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.And.
func AndInt64
func AndInt64(addr *int64, mask int64) (old int64)
AndInt64 атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.And вместо этого.
func AndUint32
func AndUint32(addr *uint32, mask uint32) (old uint32)
AndUint32 атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.And вместо него.
func AndUint64
func AndUint64(addr *uint64, mask uint64) (old uint64)
AndUint64 атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.And вместо этого.
func AndUintptr
func AndUintptr(addr *uintptr, mask uintptr) (old uintptr)
AndUintptr атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.And вместо этого.
func CompareAndSwapInt32
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
CompareAndSwapInt32 выполняет операцию сравнения и замены для значения int32. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.CompareAndSwap.
func CompareAndSwapInt64
func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
CompareAndSwapInt64 выполняет операцию сравнения и замены для значения int64. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.CompareAndSwap (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func CompareAndSwapPointer
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)
CompareAndSwapPointer выполняет операцию сравнения и обмена для значения unsafe.Pointer. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Pointer.CompareAndSwap.
func CompareAndSwapUint32
func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)
CompareAndSwapUint32 выполняет операцию сравнения и замены для значения uint32. Рассмотрите возможность использования более удобной и менее подверженной ошибкам функции Uint32.CompareAndSwap.
func CompareAndSwapUint64
func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)
CompareAndSwapUint64 выполняет операцию сравнения и замены для значения uint64. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.CompareAndSwap (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func CompareAndSwapUintptr
func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)
CompareAndSwapUintptr выполняет операцию сравнения и замены для значения uintptr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.CompareAndSwap.
func LoadInt32
func LoadInt32(addr *int32) (val int32)
LoadInt32 атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Load.
func LoadInt64
func LoadInt64(addr *int64) (val int64)
LoadInt64 атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Load (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func LoadPointer
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
LoadPointer атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Pointer.Load.
func LoadUint32
func LoadUint32(addr *uint32) (val uint32)
LoadUint32 атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Load.
func LoadUint64
func LoadUint64(addr *uint64) (val uint64)
LoadUint64 атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Load (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func LoadUintptr
func LoadUintptr(addr *uintptr) (val uintptr)
LoadUintptr атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Load.
func OrInt32
func OrInt32(addr *int32, mask int32) (old int32)
OrInt32 атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, указанную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Or.
func OrInt64
func OrInt64(addr *int64, mask int64) (old int64)
OrInt64 атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Or вместо него.
func OrUint32
func OrUint32(addr *uint32, mask uint32) (старый uint32)
OrUint32 атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Or.
func OrUint64
func OrUint64(addr *uint64, mask uint64) (old uint64)
OrUint64 атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Or вместо него.
func OrUintptr
func OrUintptr(addr *uintptr, mask uintptr) (old uintptr)
OrUintptr атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Or.
func StoreInt32
func StoreInt32(addr *int32, val int32)
StoreInt32 атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Store.
func StoreInt64
func StoreInt64(addr *int64, val int64)
StoreInt64 атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Store (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func StorePointer
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
StorePointer атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Pointer.Store.
func StoreUint32
func StoreUint32(addr *uint32, val uint32)
StoreUint32 атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Store.
func StoreUint64
func StoreUint64(addr *uint64, val uint64)
StoreUint64 атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Store (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func StoreUintptr
func StoreUintptr(addr *uintptr, val uintptr)
StoreUintptr атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Store.
func SwapInt32
func SwapInt32(addr *int32, new int32) (old int32)
SwapInt32 атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Swap.
func SwapInt64
func SwapInt64(addr *int64, new int64) (old int64)
SwapInt64 атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Swap (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func SwapPointer
func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)
SwapPointer атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Pointer.Swap.
func SwapUint32
func SwapUint32(addr *uint32, new uint32) (old uint32)
SwapUint32 атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Swap.
func SwapUint64
func SwapUint64(addr *uint64, new uint64) (old uint64)
SwapUint64 атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Swap (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func SwapUintptr
func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)
SwapUintptr атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Swap.
Типы
type Bool
type Bool struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Bool — это атомарное булево значение. Нулевое значение — false.
func (*Bool) CompareAndSwap
func (x *Bool) CompareAndSwap(old, new bool) (swapped bool)
CompareAndSwap выполняет операцию сравнения и замены для булевого значения x.
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var flag atomic.Bool
flag.Store(false)
// Пытаемся изменить false → true (успешно)
swapped := flag.CompareAndSwap(false, true)
fmt.Println("Swapped (false→true):", swapped, "Flag:", flag.Load())
// Пытаемся изменить false → true (неуспешно, текущее значение уже true)
swapped = flag.CompareAndSwap(false, true)
fmt.Println("Swapped (false→true):", swapped, "Flag:", flag.Load())
}
func (*Bool) Load
func (x *Bool) Load() bool
Load атомарно загружает и возвращает значение, хранящееся в x.
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
)
func main() {
var ready atomic.Bool
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(500 * time.Millisecond)
ready.Store(true)
}()
for !ready.Load() {
fmt.Println("Waiting...")
time.Sleep(100 * time.Millisecond)
}
fmt.Println("Ready!")
wg.Wait()
}
func (*Bool) Store
func (x *Bool) Store(val bool)
Store атомарно сохраняет val в x.
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var initialized atomic.Bool
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
if initialized.Load() {
fmt.Printf("Worker %d: already initialized\n", id)
return
}
initialized.Store(true)
fmt.Printf("Worker %d: did initialization\n", id)
}(i)
}
wg.Wait()
}
func (*Bool) Swap
func (x *Bool) Swap(new bool) (old bool)
Swap атомарно сохраняет new в x и возвращает предыдущее значение.
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var toggle atomic.Bool
toggle.Store(false)
// Переключаем значение и получаем старое
old := toggle.Swap(true)
fmt.Println("Old value:", old, "New value:", toggle.Load())
// Переключаем ещё раз
old = toggle.Swap(false)
fmt.Println("Old value:", old, "New value:", toggle.Load())
}
type Int32/Int64/Uint32/Uint64/Uintptr
type Int32 struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Int32 — это атомарное целое число int32. Нулевое значение — ноль. Int64 — это атомарное целое число int64. Нулевое значение — ноль. Uint32 - это атомарный uint32. Нулевое значение равно нулю. Uint64 - это атомарный uint64. Нулевое значение равно нулю. Uintptr - это атомарный uintptr. Нулевое значение равно нулю.
Пример
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var num atomic.Int32
// Store - установка начального значения
num.Store(10)
fmt.Printf("Store(10): %d\n", num.Load())
// Add - атомарное добавление
newVal := num.Add(5)
fmt.Printf("Add(5): new=%d, loaded=%d\n", newVal, num.Load())
// Swap - замена значения
old := num.Swap(20)
fmt.Printf("Swap(20): old=%d, new=%d\n", old, num.Load())
// CompareAndSwap - условная замена
swapped := num.CompareAndSwap(20, 30)
fmt.Printf("CompareAndSwap(20→30): %t, value=%d\n", swapped, num.Load())
// Пытаемся сделать замену с неверным old значением
swapped = num.CompareAndSwap(20, 40)
fmt.Printf("CompareAndSwap(20→40): %t, value=%d\n", swapped, num.Load())
// Побитовые операции
// Устанавливаем значение для битовых операций
num.Store(0b00001111) // 15 в десятичной
// And - побитовое И
old = num.And(0b00110011) // 0b00000011 = 3
fmt.Printf("And(0b00110011): old=0b%08b, new=0b%08b (%d)\n",
old, num.Load(), num.Load())
// Or - побитовое ИЛИ
old = num.Or(0b11001100) // 0b11001111 = 207
fmt.Printf("Or(0b11001100): old=0b%08b, new=0b%08b (%d)\n",
old, num.Load(), num.Load())
// Load - просто чтение
current := num.Load()
fmt.Printf("Load(): %d (0b%08b)\n", current, current)
}
Вывод программы:
Store(10): 10
Add(5): new=15, loaded=15
Swap(20): old=15, new=20
CompareAndSwap(20→30): true, value=30
CompareAndSwap(20→40): false, value=30
And(0b00110011): old=0b00001111, new=0b00000011 (3)
Or(0b11001100): old=0b00000011, new=0b11001111 (207)
Load(): 207 (0b11001111)
Объяснение Uintptr
atomic.Uintptr в Go
atomic.Uintptr — это тип, добавленный в Go 1.19, который предоставляет атомарные операции для беззнаковых целых чисел размером с указатель (uintptr).
Основное назначение
Атомарные операции с указателями:
uintptrчасто используется для низкоуровневых операций с указателямиatomic.Uintptrпозволяет безопасно работать с ними в конкурентных сценариях
Реализация lock-free структур данных:
- Полезен при создании собственных конкурентных структур данных
- Позволяет избежать блокировок при определенных операциях
Работа с небезопасными указателями:
- Когда нужно сохранить
uintptrзначение атомарно (например, при работе черезunsafe)
- Когда нужно сохранить
Ключевые особенности
- Обеспечивает атомарность операций без использования мьютексов
- Поддерживает все стандартные атомарные операции:
func (u *Uintptr) Add(delta uintptr) (new uintptr) func (u *Uintptr) Load() uintptr func (u *Uintptr) Store(val uintptr) func (u *Uintptr) Swap(new uintptr) (old uintptr) func (u *Uintptr) CompareAndSwap(old, new uintptr) (swapped bool)
Пример использования
package main
import (
"fmt"
"sync/atomic"
"unsafe"
)
type Data struct {
value int
}
func main() {
var addr atomic.Uintptr
data := &Data{value: 42}
// Сохраняем указатель как uintptr
addr.Store(uintptr(unsafe.Pointer(data)))
// Атомарно загружаем и преобразуем обратно
ptr := (*Data)(unsafe.Pointer(addr.Load()))
fmt.Println("Data value:", ptr.value) // 42
// Атомарное добавление к значению (если используется как число)
var counter atomic.Uintptr
counter.Store(100)
newVal := counter.Add(50)
fmt.Println("Counter:", newVal) // 150
}
Когда использовать
- При работе с
unsafe.Pointerи низкоуровневыми операциями - Для атомарного управления памятью или ресурсами
- В высокопроизводительных сценариях, где важна lock-free синхронизация
Отличие от других атомарных типов
Uintptrработает именно с размером указателя (архитектурно-зависимым)- Более безопасная альтернатива прямому использованию
atomic.AddUintptrи подобных функций - Типобезопасная обертка (по сравнению с использованием
atomicфункций напрямую)
Этот тип особенно полезен в системном программировании и при реализации высокопроизводительных параллельных алгоритмов.
func (*Int32) Add
func (x *Int32) Add(delta int32) (new int32)
Add атомарно добавляет delta к x и возвращает новое значение.
func (*Int32) And
func (x *Int32) And(mask int32) (old int32)
And атомарно выполняет побитовую операцию AND над x, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение.
func (*Int32) CompareAndSwap
func (x *Int32) CompareAndSwap(old, new int32) (swapped bool)
CompareAndSwap выполняет операцию сравнения и замены для x.
func (*Int32) Load
func (x *Int32) Load() int32
Load атомарно загружает и возвращает значение, хранящееся в x.
func (*Int32) Or
func (x *Int32) Or(mask int32) (old int32)
Or атомарно выполняет операцию побитового ИЛИ над x, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение.
func (*Int32) Store
func (x *Int32) Store(val int32)
Store атомарно сохраняет val в x.
func (*Int32) Swap
func (x *Int32) Swap(new int32) (old int32)
Swap атомарно сохраняет new в x и возвращает предыдущее значение.
type Pointer
type Pointer[T any] struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Pointer — это атомарный указатель типа *T. Нулевое значение — nil *T.
Пример
package main
import (
"fmt"
"sync/atomic"
)
type Data struct {
value int
}
func main() {
var ptr atomic.Pointer[Data]
// Исходные данные
data1 := &Data{value: 10}
data2 := &Data{value: 20}
data3 := &Data{value: 30}
// Store - атомарное сохранение указателя
ptr.Store(data1)
printState("Store(data1)", &ptr)
// Load - атомарное чтение указателя
current := ptr.Load()
fmt.Printf("Load(): %+v\n", current)
// Swap - атомарная замена указателя
old := ptr.Swap(data2)
printState("Swap(data2)", &ptr)
fmt.Printf("Old value: %+v\n", old)
// CompareAndSwap успешный (текущее значение = data2)
swapped := ptr.CompareAndSwap(data2, data3)
printState("CompareAndSwap(data2→data3)", &ptr)
fmt.Printf("Swapped: %v\n", swapped)
// CompareAndSwap неудачный (текущее значение уже data3)
swapped = ptr.CompareAndSwap(data2, data1)
printState("CompareAndSwap(data2→data1) [expected fail]", &ptr)
fmt.Printf("Swapped: %v\n", swapped)
// Демонстрация конкурентного доступа
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for i := 0; i < 1000; i++ {
ptr.Store(&Data{value: i})
}
}()
go func() {
defer wg.Done()
for i := 0; i < 1000; i++ {
_ = ptr.Load()
}
}()
wg.Wait()
fmt.Println("Concurrent access completed")
}
func printState(op string, p *atomic.Pointer[Data]) {
fmt.Printf("%-30s: %+v\n", op, p.Load())
}
Вывод программы:
Store(data1) : &{value:10}
Load(): &{value:10}
Swap(data2) : &{value:20}
Old value: &{value:10}
CompareAndSwap(data2→data3) : &{value:30}
Swapped: true
CompareAndSwap(data2→data1) [expected fail]: &{value:30}
Swapped: false
Concurrent access completed
func (*Pointer[T]) CompareAndSwap
func (x *Pointer[T]) CompareAndSwap(old, new *T) (swapped bool)
CompareAndSwap выполняет операцию сравнения и замены для x.
func (*Pointer[T]) Load
func (x *Pointer[T]) Load() *T
Load атомарно загружает и возвращает значение, хранящееся в x.
func (*Pointer[T]) Store
func (x *Pointer[T]) Store(val *T)
Store атомарно сохраняет val в x.
func (*Pointer[T]) Swap
func (x *Pointer[T]) Swap(new *T) (old *T)
Swap атомарно сохраняет new в x и возвращает предыдущее значение.
type Value
type Value struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Value обеспечивает атомарную загрузку и хранение значения с постоянным типом. Нулевое значение для Value возвращает nil из Value.Load. После вызова Value.Store Value не должен копироваться.
Value не должен копироваться после первого использования.
Объяснение Value
atomic.Value — это контейнер для безопасного хранения и обмена значениями между горутинами. Главная особенность — все операции с ним атомарны (не требуют мьютексов).
Основные правила:
- После первого использования нельзя копировать Value
- Все хранимые значения должны быть одного типа
- Нельзя хранить nil
Примеры использования:
1. Базовое использование (Store/Load)
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var config atomic.Value
// Сохраняем конфиг
config.Store(map[string]string{
"host": "localhost",
"port": "8080",
})
// Читаем конфиг в другой горутине
go func() {
c := config.Load().(map[string]string)
fmt.Println("Config in goroutine:", c["host"])
}()
// Читаем в main
fmt.Println("Port:", config.Load().(map[string]string)["port"])
}
2. Swap (атомарная замена)
var lastUser atomic.Value
func updateUser(user string) {
old := lastUser.Swap(user)
fmt.Printf("User changed from %v to %v\n", old, user)
}
func main() {
updateUser("Alice") // User changed from <nil> to Alice
updateUser("Bob") // User changed from Alice to Bob
}
3. CompareAndSwap (условная замена)
var currentID atomic.Value
func updateID(expected, new int) bool {
return currentID.CompareAndSwap(expected, new)
}
func main() {
currentID.Store(100)
success := updateID(100, 200)
fmt.Println("Update 100→200:", success) // true
success = updateID(100, 300)
fmt.Println("Update 100→300:", success) // false (текущее значение уже 200)
}
4. Хранение структур
type Config struct {
Timeout int
Mode string
}
func main() {
var cfg atomic.Value
cfg.Store(Config{Timeout: 30, Mode: "production"})
// Обновление конфига
newCfg := cfg.Load().(Config)
newCfg.Timeout = 60
cfg.Store(newCfg)
}
Когда использовать:
- Для “горячей” замены конфигурации без блокировок
- При частом чтении и редком обновлении данных
- Для реализации кэшей с атомарным обновлением
На заметку
- При Load всегда нужно делать type assertion (
.(type)) - Тип значения нельзя менять после первого Store
- Для сложных структур лучше хранить указатели (но не nil)
Пример config
package main
import (
"fmt"
"math/rand"
"sync/atomic"
"time"
)
// ServerConfig содержит конфигурацию сервера
type ServerConfig struct {
Host string
Port int
Timeout time.Duration
Debug bool
}
var (
config atomic.Value // Текущая конфигурация
counter atomic.Int32 // Счетчик запросов
)
func loadConfig() ServerConfig {
// Имитация загрузки конфигурации (в реальности может быть из файла, БД и т.д.)
rand.Seed(time.Now().UnixNano())
return ServerConfig{
Host: fmt.Sprintf("server-%d.example.com", rand.Intn(3)+1),
Port: 8000 + rand.Intn(100),
Timeout: time.Duration(rand.Intn(5)+1) * time.Second,
Debug: rand.Intn(2) == 0,
}
}
func processRequest(workerID int) {
for {
// Имитация обработки запроса
time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
// Атомарно получаем текущую конфигурацию
conf := config.Load().(ServerConfig)
reqID := counter.Add(1)
fmt.Printf("Worker %d processing request #%d with config: %+v\n",
workerID, reqID, conf)
}
}
func configUpdater() {
for {
time.Sleep(5 * time.Second)
newConfig := loadConfig()
config.Store(newConfig)
fmt.Printf("\nConfig updated: %+v\n\n", newConfig)
}
}
func main() {
// Инициализация начальной конфигурации
config.Store(loadConfig())
fmt.Println("Initial config:", config.Load())
// Запускаем обновление конфигурации в фоне
go configUpdater()
// Запускаем воркеров для обработки запросов
for i := 1; i <= 5; i++ {
go processRequest(i)
}
// Даем поработать 30 секунд
time.Sleep(30 * time.Second)
fmt.Println("Server shutting down...")
}
Вывод программы:
Initial config: {Host:server-3.example.com Port:8034 Timeout:4s Debug:false}
Worker 5 processing request #1 with config: {Host:server-3.example.com Port:8034 Timeout:4s Debug:false}
Worker 3 processing request #2 with config: {Host:server-3.example.com Port:8034 Timeout:4s Debug:false}
Config updated: {Host:server-1.example.com Port:8067 Timeout:2s Debug:true}
Worker 1 processing request #3 with config: {Host:server-1.example.com Port:8067 Timeout:2s Debug:true}
Worker 4 processing request #4 with config: {Host:server-1.example.com Port:8067 Timeout:2s Debug:true}
Пример ReadMostly
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
)
// ConcurrentMap реализует потокобезопасную map с помощью atomic.Value
type ConcurrentMap struct {
data atomic.Value // хранит map[string]string
mu sync.Mutex // только для записи
}
// NewConcurrentMap создает новую потокобезопасную map
func NewConcurrentMap() *ConcurrentMap {
cm := &ConcurrentMap{}
cm.data.Store(make(map[string]string))
return cm
}
// Get безопасно получает значение по ключу
func (cm *ConcurrentMap) Get(key string) (string, bool) {
currentMap := cm.data.Load().(map[string]string)
val, ok := currentMap[key]
return val, ok
}
// Set безопасно устанавливает значение по ключу
func (cm *ConcurrentMap) Set(key, value string) {
cm.mu.Lock()
defer cm.mu.Unlock()
// Копируем текущую map
oldMap := cm.data.Load().(map[string]string)
newMap := make(map[string]string, len(oldMap)+1)
// Копируем все элементы
for k, v := range oldMap {
newMap[k] = v
}
// Добавляем/обновляем элемент
newMap[key] = value
// Атомарно заменяем map
cm.data.Store(newMap)
}
// Delete безопасно удаляет элемент по ключу
func (cm *ConcurrentMap) Delete(key string) {
cm.mu.Lock()
defer cm.mu.Unlock()
oldMap := cm.data.Load().(map[string]string)
if _, exists := oldMap[key]; !exists {
return
}
newMap := make(map[string]string, len(oldMap))
for k, v := range oldMap {
if k != key {
newMap[k] = v
}
}
cm.data.Store(newMap)
}
func main() {
config := NewConcurrentMap()
// Записываем начальные значения
config.Set("host", "localhost")
config.Set("port", "8080")
config.Set("timeout", "30s")
// Запускаем 5 читателей
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < 3; j++ {
time.Sleep(time.Millisecond * 100)
if val, ok := config.Get("host"); ok {
fmt.Printf("Reader %d: host=%s\n", id, val)
}
}
}(i)
}
// Запускаем 2 писателя
for i := 0; i < 2; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < 2; j++ {
time.Sleep(time.Millisecond * 200)
newHost := fmt.Sprintf("server-%d-%d", id, j)
config.Set("host", newHost)
fmt.Printf("Writer %d updated host to %s\n", id, newHost)
}
}(i)
}
// Запускаем удаление через некоторое время
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(time.Millisecond * 300)
config.Delete("timeout")
fmt.Println("Deleted timeout key")
}()
wg.Wait()
// Финальное состояние
fmt.Println("\nFinal config:")
if host, ok := config.Get("host"); ok {
fmt.Println("host:", host)
}
if port, ok := config.Get("port"); ok {
fmt.Println("port:", port)
}
if _, ok := config.Get("timeout"); !ok {
fmt.Println("timeout: (deleted)")
}
}
Вывод программы:
Reader 4: host=localhost
Reader 0: host=localhost
Reader 2: host=localhost
Reader 1: host=localhost
Writer 0 updated host to server-0-0
Reader 3: host=server-0-0
Reader 0: host=server-0-0
Reader 4: host=server-0-0
Writer 1 updated host to server-1-0
Reader 2: host=server-1-0
Deleted timeout key
Reader 1: host=server-1-0
Writer 0 updated host to server-0-1
Writer 1 updated host to server-1-1
Reader 3: host=server-1-1
Final config:
host: server-1-1
port: 8080
timeout: (deleted)
func (*Value) CompareAndSwap
func (v *Value) CompareAndSwap(old, new any) (swapped bool)
CompareAndSwap выполняет операцию сравнения и замены для Value.
Все вызовы CompareAndSwap для данного значения должны использовать значения одного и того же конкретного типа. CompareAndSwap несовместимого типа вызывает панику, как и CompareAndSwap(old, nil).
func (*Value) Load
func (v *Value) Load() (val any)
Load возвращает значение, установленное последним Store. Он возвращает nil, если для этого значения не было вызова Store.
func (*Value) Store
func (v *Value) Store(val any)
Store устанавливает значение Value v равным val. Все вызовы Store для данного Value должны использовать значения одного и того же конкретного типа. Store несовместимого типа вызывает панику, как и Store(nil).
func (*Value) Swap
func (v *Value) Swap(new any) (old any)
Swap сохраняет new в Value и возвращает предыдущее значение. Он возвращает nil, если Value пуст.
Все вызовы Swap для данного Value должны использовать значения одного и того же конкретного типа. Swap несовместимого типа вызывает панику, как и Swap(nil).
11 - Пакет os языка программирования Go
Пакет os предоставляет платформонезависимый интерфейс к функциональности операционной системы. Дизайн Unix-подобный, хотя обработка ошибок Go-подобная; неудачные вызовы возвращают значения типа error, а не номера ошибок. Часто в ошибке содержится больше информации. Например, если вызов, принимающий имя файла, не работает, например Open или Stat, ошибка будет включать имя файла, который не работает, и будет иметь тип *PathError, который может быть распакован для получения дополнительной информации.
Интерфейс os должен быть единообразным для всех операционных систем. Функции, которые не являются общедоступными, представлены в специфическом для системы пакете syscall.
Приведем простой пример: открытие файла и чтение его части.
file, err := os.Open("file.go") // For read access.
if err != nil {
log.Fatal(err)
}
Если открытие завершится неудачей, строка ошибки будет иметь понятный вид, например
open file.go: no such file or directory
Затем данные файла могут быть считаны в виде фрагмента байтов. Команды Read и Write берут количество байтов из длины фрагмента аргумента.
data := make([]byte, 100)
count, err := file.Read(data)
if err != nil {
log.Fatal(err)
}
fmt.Printf("read %d bytes: %q\n", count, data[:count])
Concurrency (Параллелизм)
Методы File соответствуют операциям файловой системы. Все они безопасны для одновременного использования. Максимальное количество одновременных операций над файлом может быть ограничено ОС или системой. Это число должно быть большим, но его превышение может снизить производительность или вызвать другие проблемы.
Константы
const (
// Должно быть указано ровно одно из значений O_RDONLY, O_WRONLY или O_RDWR.
O_RDONLY int = syscall.O_RDONLY // открыть файл только для чтения.
O_WRONLY int = syscall.O_WRONLY // открыть файл только для записи.
O_RDWR int = syscall.O_RDWR // открыть файл для чтения и записи.
// Остальные значения могут быть объединены с помощью оператора OR для управления поведением.
O_APPEND int = syscall.O_APPEND // добавлять данные в файл при записи.
O_CREATE int = syscall.O_CREAT // создать новый файл, если его нет.
O_EXCL int = syscall.O_EXCL // используется с O_CREATE, файл не должен существовать.
O_SYNC int = syscall.O_SYNC // открыть для синхронного ввода-вывода.
O_TRUNC int = syscall.O_TRUNC // усечь обычный файл с правом записи при открытии.
)
Флаги для OpenFile, оборачивающие флаги базовой системы. Не все флаги могут быть реализованы в данной системе.
Устарело
const (
SEEK_SET int = 0 // поиск относительно начала файла
SEEK_CUR int = 1 // поиск относительно текущего смещения
SEEK_END int = 2 // поиск относительно конца
)
Значения поиска.
Устарело: используйте io.SeekStart, io.SeekCurrent и io.SeekEnd.
const (
// Одиночные буквы являются аббревиатурами,
// используемыми для форматирования методом String.
ModeDir = fs.ModeDir // d: каталог
ModeAppend = fs.ModeAppend // a: только добавление
ModeExclusive = fs.ModeExclusive // l: исключительное использование
ModeTemporary = fs.ModeTemporary // T: временный файл; только Plan 9
ModeSymlink = fs.ModeSymlink // L: символическая ссылка
ModeDevice = fs.ModeDevice // D: файл устройства
ModeNamedPipe = fs.ModeNamedPipe // p: именованный канал (FIFO)
ModeSocket = fs.ModeSocket // S: сокет домена Unix
ModeSetuid = fs.ModeSetuid // u: setuid
ModeSetgid = fs.ModeSetgid // g: setgid
ModeCharDevice = fs.ModeCharDevice // c: символьное устройство Unix, когда установлен ModeDevice
ModeSticky = fs.ModeSticky // t: sticky
ModeIrregular = fs.ModeIrregular // ?: нерегулярный файл; ничего больше не известно об этом файле
// Маска для битов типа. Для обычных файлов не будет установлено ничего.
ModeType = fs.ModeType
ModePerm = fs.ModePerm // Биты разрешений Unix, 0o777
)
Определенные биты режима файла являются наиболее значимыми битами FileMode. Девять наименее значимых битов являются стандартными разрешениями Unix rwxrwxrwx. Значения этих битов следует рассматривать как часть общедоступного API и могут использоваться в протоколах передачи данных или представлениях диска: их нельзя изменять, хотя могут быть добавлены новые биты.
const DevNull = "/dev/null"
DevNull — это имя «нулевого устройства» операционной системы. В Unix-подобных системах оно имеет вид «/dev/null», в Windows — «NUL».
Переменные
var (
// ErrInvalid указывает на недействительный аргумент.
// Методы File возвращают эту ошибку, когда получатель равен nil.
ErrInvalid = fs.ErrInvalid // «недействительный аргумент»
ErrPermission = fs.ErrPermission // «разрешение отказано»
ErrExist = fs.ErrExist // «файл уже существует»
ErrNotExist = fs.ErrNotExist // «файл не существует»
ErrClosed = fs.ErrClosed // «файл уже закрыт»
ErrNoDeadline = errNoDeadline() // «тип файла не поддерживает срок»
ErrDeadlineExceeded = errDeadlineExceeded() // «тайм-аут ввода-вывода»
)
Портативные аналоги некоторых распространенных ошибок системных вызовов.
Ошибки, возвращаемые из этого пакета, можно проверить на наличие этих ошибок с помощью errors.Is.
var (
Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr"))
Stdin, Stdout и Stderr — открытые файлы, указывающие на дескрипторы стандартного ввода, стандартного вывода и стандартных ошибок.
Обратите внимание, что среда выполнения Go записывает в стандартный файл ошибок сообщения о паниках и сбоях; закрытие Stderr может привести к тому, что эти сообщения будут отправляться в другое место, например в файл, открытый позже.
var Args []string
Args содержит аргументы командной строки, начиная с имени программы.
var ErrProcessDone = errors.New("os: process already finished")
ErrProcessDone указывает, что процесс завершен.
11.1 - Описание функций пакета os
func Chdir
func Chdir(dir string) error
Chdir изменяет текущий рабочий каталог на указанный каталог. Если произошла ошибка, она будет типа *PathError.
func Chmod
func Chmod(name string, mode FileMode) error
Chmod изменяет режим указанного файла на mode. Если файл является символической ссылкой, то изменяется режим цели ссылки. Если произошла ошибка, то она будет типа *PathError.
В зависимости от операционной системы используется разный поднабор битов режима.
В Unix используются биты разрешений режима ModeSetuid, ModeSetgid и ModeSticky.
В Windows используется только бит 0o200 (доступ на запись для владельца) режима; он контролирует, установлен ли или снят атрибут «только для чтения» файла. Остальные биты в настоящее время не используются. Для совместимости с Go 1.12 и более ранними версиями используйте режим, отличный от нуля. Используйте режим 0o400 для файла, доступного только для чтения, и 0o600 для файла, доступного для чтения и записи.
В Plan 9 используются биты разрешений режима ModeAppend, ModeExclusive и ModeTemporary.
Пример
package main
import (
"log"
"os"
)
func main() {
if err := os.Chmod("some-filename", 0644); err != nil {
log.Fatal(err)
}
}
func Chown
func Chown(name string, uid, gid int) error
Chown изменяет числовые значения uid и gid указанного файла. Если файл является символической ссылкой, он изменяет uid и gid цели ссылки. uid или gid равные -1 означают, что это значение не изменяется. Если происходит ошибка, она будет типа *PathError.
В Windows или Plan 9 Chown всегда возвращает ошибку syscall.EWINDOWS или EPLAN9, обернутую в *PathError.
func Chtimes
func Chtimes(name string, atime time.Time, mtime time.Time) error
Chtimes изменяет время доступа и изменения указанного файла, аналогично функциям Unix utime() или utimes(). Нулевое значение time.Time оставит время соответствующего файла без изменений.
Базовая файловая система может усечь или округлить значения до менее точной единицы времени. Если произошла ошибка, она будет типа *PathError.
Пример
package main
import (
"log"
"os"
"time"
)
func main() {
mtime := time.Date(2006, time.February, 1, 3, 4, 5, 0, time.UTC)
atime := time.Date(2007, time.March, 2, 4, 5, 6, 0, time.UTC)
if err := os.Chtimes("some-filename", atime, mtime); err != nil {
log.Fatal(err)
}
}
func Clearenv
func Clearenv()
Clearenv удаляет все переменные окружения.
func CopyFS
func CopyFS(dir string, fsys fs.FS) error
CopyFS копирует файловую систему fsys в каталог dir, создавая dir при необходимости.
Файлы создаются с режимом 0o666 плюс любые права на выполнение из источника, а каталоги создаются с режимом 0o777 (до umask).
CopyFS не перезаписывает существующие файлы. Если имя файла в fsys уже существует в месте назначения, CopyFS вернет ошибку, так что errors.Is(err, fs.ErrExist) будет true.
Символьные ссылки в fsys не поддерживаются. При копировании из символьной ссылки возвращается *PathError с Err, установленным в ErrInvalid.
Символьные ссылки в dir прослеживаются.
Новые файлы, добавленные в fsys (в том числе, если dir является подкаталогом fsys) во время работы CopyFS, не гарантированно будут скопированы.
Копирование останавливается при возникновении первой ошибки и возвращает ее.
func DirFS
func DirFS(dir string) fs.FS
DirFS возвращает файловую систему (fs.FS) для дерева файлов, корнем которого является каталог dir.
Обратите внимание, что DirFS(«/prefix») гарантирует только то, что вызовы Open, которые он делает в операционной системе, будут начинаться с «/prefix»: DirFS(«/prefix»).Open(„file“) равно os.Open(«/prefix/file»). Поэтому, если /prefix/file является символической ссылкой, указывающей за пределы дерева /prefix, то использование DirFS не останавливает доступ больше, чем использование os.Open. Кроме того, корень fs.FS, возвращаемый для относительного пути, DirFS(«prefix»), будет затронут последующими вызовами Chdir. Поэтому DirFS не является общей заменой механизма безопасности типа chroot, когда дерево каталогов содержит произвольное содержимое.
Используйте Root.FS, чтобы получить fs.FS, который предотвращает выход из дерева через символьные ссылки.
Каталог dir не должен быть «».
Результат реализует io/fs.StatFS, io/fs.ReadFileFS и io/fs.ReadDirFS.
func Environ
func Environ() []string
Environ возвращает копию строк, представляющих среду, в форме «ключ=значение».
func Executable
func Executable() (string, error)
Executable возвращает имя пути к исполняемому файлу, который запустил текущий процесс. Нет гарантии, что путь по-прежнему указывает на правильный исполняемый файл. Если для запуска процесса использовалась символьная ссылка, в зависимости от операционной системы результатом может быть символьная ссылка или путь, на который она указывала. Если требуется стабильный результат, может помочь path/filepath.EvalSymlinks.
Executable возвращает абсолютный путь, если не произошла ошибка.
Основной случай использования — поиск ресурсов, расположенных относительно исполняемого файла.
func Exit
func Exit(code int)
Exit вызывает завершение текущей программы с заданным кодом состояния. По соглашению, код нуль означает успех, ненулевой — ошибку. Программа завершается немедленно; отложенные функции не выполняются.
Для обеспечения переносимости код статуса должен находиться в диапазоне [0, 125].
func Expand
func Expand(s string, mapping func(string) string) string
Expand заменяет ${var} или $var в строке на основе функции сопоставления. Например, os.ExpandEnv(s) эквивалентно os.Expand(s, os.Getenv).
Пример
package main
import (
"fmt"
"os"
)
func main() {
mapper := func(placeholderName string) string {
switch placeholderName {
case "DAY_PART":
return "morning"
case "NAME":
return "Gopher"
}
return ""
}
fmt.Println(os.Expand("Good ${DAY_PART}, $NAME!", mapper))
}
Output:
Good morning, Gopher!func ExpandEnv
func ExpandEnv(s string) string
ExpandEnv заменяет ${var} или $var в строке в соответствии со значениями текущих переменных окружения. Ссылки на неопределенные переменные заменяются пустой строкой.
Пример
package main
import (
"fmt"
"os"
)
func main() {
os.Setenv("NAME", "gopher")
os.Setenv("BURROW", "/usr/gopher")
fmt.Println(os.ExpandEnv("$NAME lives in ${BURROW}."))
}
Output:
gopher lives in /usr/gopher.func Getegid
func Getegid() int
Getegid возвращает числовой идентификатор эффективной группы вызывающего.
В Windows возвращает -1.
func Getenv
func Getenv(key string) string
Getenv извлекает значение переменной окружения, указанной ключом. Возвращает значение, которое будет пустым, если переменная отсутствует. Чтобы отличить пустое значение от не заданного значения, используйте LookupEnv.
Пример
package main
import (
"fmt"
"os"
)
func main() {
os.Setenv("NAME", "gopher")
os.Setenv("BURROW", "/usr/gopher")
fmt.Printf("%s lives in %s.\n", os.Getenv("NAME"), os.Getenv("BURROW"))
}
Output:
gopher lives in /usr/gopher.func Geteuid
func Geteuid() int
Geteuid возвращает числовой эффективный идентификатор пользователя вызывающего.
В Windows возвращает -1.
func Getgid
func Getgid() int
Getgid возвращает числовой идентификатор группы вызывающего.
В Windows возвращает -1.
func Getgroups
func Getgroups() ([]int, error)
Getgroups возвращает список числовых идентификаторов групп, к которым принадлежит вызывающий.
В Windows возвращает syscall.EWINDOWS. Возможную альтернативу см. в пакете os/user.
func Getpagesize
func Getpagesize() int
Getpagesize возвращает размер страницы памяти базовой системы.
func Getpid
func Getpid() int
Getpid возвращает идентификатор процесса вызывающего.
func Getppid
func Getppid() int
Getppid возвращает идентификатор процесса родителя вызывающего.
func Getuid
func Getuid() int
Getuid возвращает числовой идентификатор пользователя вызывающего.
В Windows возвращает -1.
func Getwd
func Getwd() (dir string, err error)
Getwd возвращает абсолютное имя пути, соответствующее текущему каталогу. Если к текущему каталогу можно перейти по нескольким путям (из-за символьных ссылок), Getwd может вернуть любой из них.
На платформах Unix, если переменная окружения PWD предоставляет абсолютное имя, и оно является именем текущего каталога, оно возвращается.
func Hostname
func Hostname() (name string, err error)
Hostname возвращает имя хоста, сообщаемое ядром.
func IsExist
func IsExist(err error) bool
IsExist возвращает булево значение, указывающее, известно ли, что файл или каталог уже существует. Оно удовлетворяется ErrExist, а также некоторыми ошибками системных вызовов.
Эта функция предшествует errors.Is. Она поддерживает только ошибки, возвращаемые пакетом os. В новом коде следует использовать errors.Is(err, fs.ErrExist).
func IsNotExist
func IsNotExist(err error) bool
IsNotExist возвращает булево значение, указывающее, известно ли, что аргумент сообщает о том, что файл или каталог не существует. Это условие выполняется для ErrNotExist, а также для некоторых ошибок системных вызовов.
Эта функция предшествует errors.Is. Она поддерживает только ошибки, возвращаемые пакетом os. В новом коде следует использовать errors.Is(err, fs.ErrNotExist).
func IsPathSeparator
func IsPathSeparator(c uint8) bool
IsPathSeparator сообщает, является ли c символом разделителя каталогов.
func IsPermission
func IsPermission(err error) bool
IsPermission возвращает булево значение, указывающее, известно ли, что аргумент сообщает об отказе в разрешении. Это условие выполняется ErrPermission, а также некоторыми ошибками системных вызовов.
Эта функция предшествует errors.Is. Она поддерживает только ошибки, возвращаемые пакетом os. В новом коде следует использовать errors.Is(err, fs.ErrPermission).
func IsTimeout
func IsTimeout(err error) bool
IsTimeout возвращает булево значение, указывающее, известно ли, что аргумент сообщает о возникновении таймаута.
Эта функция предшествует errors.Is, и понятие о том, указывает ли ошибка на тайм-аут, может быть неоднозначным. Например, ошибка Unix EWOULDBLOCK иногда указывает на тайм-аут, а иногда нет. В новом коде следует использовать errors.Is со значением, соответствующим вызову, возвращающему ошибку, например os.ErrDeadlineExceeded.
func Lchown
func Lchown(name string, uid, gid int) error
Lchown изменяет числовые значения uid и gid указанного файла. Если файл является символической ссылкой, то изменяются значения uid и gid самой ссылки. Если происходит ошибка, то она будет типа *PathError.
В Windows всегда возвращается ошибка syscall.EWINDOWS, обернутая в *PathError.
func Link
func Link(oldname, newname string) error
Link создает newname как жесткую ссылку на файл oldname. Если происходит ошибка, она будет типа *LinkError.
func LookupEnv
func LookupEnv(key string) (string, bool)
LookupEnv извлекает значение переменной окружения, указанной ключом. Если переменная присутствует в окружении, возвращается значение (которое может быть пустым) и булево значение true. В противном случае возвращаемое значение будет пустым, а булево значение будет false.
Пример
package main
import (
"fmt"
"os"
)
func main() {
show := func(key string) {
val, ok := os.LookupEnv(key)
if !ok {
fmt.Printf("%s not set\n", key)
} else {
fmt.Printf("%s=%s\n", key, val)
}
}
os.Setenv("SOME_KEY", "value")
os.Setenv("EMPTY_KEY", "")
show("SOME_KEY")
show("EMPTY_KEY")
show("MISSING_KEY")
}
Output:
SOME_KEY=value
EMPTY_KEY=
MISSING_KEY not set
func Mkdir
func Mkdir(name string, perm FileMode) error
Mkdir создает новый каталог с указанным именем и битами разрешений (до umask). Если произошла ошибка, она будет типа *PathError.
Пример
package main
import (
"log"
"os"
)
func main() {
err := os.Mkdir("testdir", 0750)
if err != nil && !os.IsExist(err) {
log.Fatal(err)
}
err = os.WriteFile("testdir/testfile.txt", []byte("Hello, Gophers!"), 0660)
if err != nil {
log.Fatal(err)
}
}
func MkdirAll
func MkdirAll(path string, perm FileMode) error
MkdirAll создает каталог с именем path, а также все необходимые родительские каталоги, и возвращает nil, или же возвращает ошибку. Биты прав доступа perm (до umask) используются для всех каталогов, которые создает MkdirAll. Если path уже является каталогом, MkdirAll ничего не делает и возвращает nil.
Пример
package main
import (
"log"
"os"
)
func main() {
err := os.MkdirAll("test/subdir", 0750)
if err != nil {
log.Fatal(err)
}
err = os.WriteFile("test/subdir/testfile.txt", []byte("Hello, Gophers!"), 0660)
if err != nil {
log.Fatal(err)
}
}
func MkdirTemp
func MkdirTemp(dir, pattern string) (string, error)
MkdirTemp создает новый временный каталог в каталоге dir и возвращает путь к новому каталогу. Имя нового каталога генерируется путем добавления случайной строки в конец pattern. Если pattern содержит “*”, случайная строка заменяет последний “*”. Каталог создается с режимом 0o700 (до umask). Если dir — пустая строка, MkdirTemp использует каталог по умолчанию для временных файлов, возвращаемый TempDir. Несколько программ или goroutines, вызывающих MkdirTemp одновременно, не будут выбирать один и тот же каталог. Ответственность за удаление каталога, когда он больше не нужен, лежит на вызывающем.
Пример
package main
import (
"log"
"os"
"path/filepath"
)
func main() {
dir, err := os.MkdirTemp("", "example")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir) // clean up
file := filepath.Join(dir, "tmpfile")
if err := os.WriteFile(file, []byte("content"), 0666); err != nil {
log.Fatal(err)
}
}
Пример suffix
package main
import (
"log"
"os"
"path/filepath"
)
func main() {
logsDir, err := os.MkdirTemp("", "*-logs")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(logsDir) // clean up
// Logs can be cleaned out earlier if needed by searching
// for all directories whose suffix ends in *-logs.
globPattern := filepath.Join(os.TempDir(), "*-logs")
matches, err := filepath.Glob(globPattern)
if err != nil {
log.Fatalf("Failed to match %q: %v", globPattern, err)
}
for _, match := range matches {
if err := os.RemoveAll(match); err != nil {
log.Printf("Failed to remove %q: %v", match, err)
}
}
}
func NewSyscallError
func NewSyscallError(syscall string, err error) error
NewSyscallError возвращает в качестве ошибки новый SyscallError с указанным именем системного вызова и подробностями ошибки. Для удобства, если err равен nil, NewSyscallError возвращает nil.
func Pipe
func Pipe() (r *File, w *File, err error)
Pipe возвращает соединенную пару файлов; считывает из r байты, записанные в w. Он возвращает файлы и ошибку, если таковая имеется.
func ReadFile
func ReadFile(name string) ([]byte, error)
ReadFile считывает файл с указанным именем и возвращает его содержимое. Успешный вызов возвращает err == nil, а не err == EOF. Поскольку ReadFile считывает весь файл, он не рассматривает EOF из Read как ошибку, о которой следует сообщать.
Пример
package main
import (
"log"
"os"
)
func main() {
data, err := os.ReadFile("testdata/hello")
if err != nil {
log.Fatal(err)
}
os.Stdout.Write(data)
}
Output:
Hello, Gophers!func Readlink
func Readlink(name string) (string, error)
Readlink возвращает место назначения символической ссылки с указанным именем. Если произошла ошибка, она будет типа *PathError.
Если место назначения ссылки является относительным, Readlink возвращает относительный путь, не преобразуя его в абсолютный.
Пример
package main
import (
"errors"
"fmt"
"log"
"os"
"path/filepath"
)
func main() {
// First, we create a relative symlink to a file.
d, err := os.MkdirTemp("", "")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(d)
targetPath := filepath.Join(d, "hello.txt")
if err := os.WriteFile(targetPath, []byte("Hello, Gophers!"), 0644); err != nil {
log.Fatal(err)
}
linkPath := filepath.Join(d, "hello.link")
if err := os.Symlink("hello.txt", filepath.Join(d, "hello.link")); err != nil {
if errors.Is(err, errors.ErrUnsupported) {
// Allow the example to run on platforms that do not support symbolic links.
fmt.Printf("%s links to %s\n", filepath.Base(linkPath), "hello.txt")
return
}
log.Fatal(err)
}
// Readlink returns the relative path as passed to os.Symlink.
dst, err := os.Readlink(linkPath)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s links to %s\n", filepath.Base(linkPath), dst)
var dstAbs string
if filepath.IsAbs(dst) {
dstAbs = dst
} else {
// Symlink targets are relative to the directory containing the link.
dstAbs = filepath.Join(filepath.Dir(linkPath), dst)
}
// Check that the target is correct by comparing it with os.Stat
// on the original target path.
dstInfo, err := os.Stat(dstAbs)
if err != nil {
log.Fatal(err)
}
targetInfo, err := os.Stat(targetPath)
if err != nil {
log.Fatal(err)
}
if !os.SameFile(dstInfo, targetInfo) {
log.Fatalf("link destination (%s) is not the same file as %s", dstAbs, targetPath)
}
}
Output:
hello.link links to hello.txtfunc Remove
func Remove(name string) error
Remove удаляет указанный файл или (пустой) каталог. Если происходит ошибка, она будет типа *PathError.
func RemoveAll
func RemoveAll(path string) error
RemoveAll удаляет путь и все вложенные в него элементы. Он удаляет все, что может, но возвращает первую встреченную ошибку. Если путь не существует, RemoveAll возвращает nil (без ошибки). Если произошла ошибка, она будет типа *PathError.
func Rename
func Rename(oldpath, newpath string) error
Rename переименовывает (перемещает) oldpath в newpath. Если newpath уже существует и не является каталогом, Rename заменяет его. Если newpath уже существует и является каталогом, Rename возвращает ошибку. Ограничения, специфичные для ОС, могут применяться, когда oldpath и newpath находятся в разных каталогах. Даже в пределах одного каталога на платформах, отличных от Unix, Rename не является атомарной операцией. Если произошла ошибка, она будет типа *LinkError.
func SameFile
func SameFile(fi1, fi2 FileInfo) bool
SameFile сообщает, описывают ли fi1 и fi2 один и тот же файл. Например, в Unix это означает, что поля device и inode двух базовых структур идентичны; в других системах решение может основываться на именах путей. SameFile применяется только к результатам, возвращаемым Stat этого пакета. В других случаях она возвращает false.
func Setenv
func Setenv(key, value string) error
Setenv устанавливает значение переменной среды, указанной ключом. Возвращает ошибку, если таковая имеется.
func Symlink
func Symlink(oldname, newname string) error
Symlink создает newname как символическую ссылку на oldname. В Windows символическая ссылка на несуществующий oldname создает файловую символическую ссылку; если oldname позже будет создан как каталог, символическая ссылка не будет работать. Если произойдет ошибка, она будет типа *LinkError.
func TempDir
func TempDir() string
TempDir возвращает каталог по умолчанию, который будет использоваться для временных файлов.
В системах Unix она возвращает $TMPDIR, если он не пуст, в противном случае — /tmp. В Windows она использует GetTempPath, возвращая первое непустое значение из %TMP%, %TEMP%, %USERPROFILE% или каталога Windows. В Plan 9 она возвращает /tmp.
Существование каталога и наличие прав доступа к нему не гарантируются.
func Truncate
func Truncate(name string, size int64) error
Truncate изменяет размер указанного файла. Если файл является символической ссылкой, изменяется размер цели ссылки. Если происходит ошибка, она будет типа *PathError.
func Unsetenv
func Unsetenv(key string) error
Unsetenv снимает установку одной переменной среды.
Пример
package main
import (
"os"
)
func main() {
os.Setenv("TMPDIR", "/my/tmp")
defer os.Unsetenv("TMPDIR")
}
func UserCacheDir
func UserCacheDir() (string, error)
UserCacheDir возвращает корневой каталог по умолчанию, который будет использоваться для кэшированных данных конкретного пользователя. Пользователи должны создать свой собственный подкаталог для конкретного приложения в этом каталоге и использовать его.
В системах Unix она возвращает $XDG_CACHE_HOME, как указано в https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html, если он не пустой, в противном случае — $HOME/.cache. В Darwin она возвращает $HOME/Library/Caches. В Windows она возвращает %LocalAppData%. В Plan 9 она возвращает $home/lib/cache.
Если местоположение не может быть определено (например, $HOME не определено) или путь в $XDG_CACHE_HOME является относительным, то будет возвращена ошибка.
Пример
import (
"log"
"os"
"path/filepath"
"sync"
)
func main() {
dir, dirErr := os.UserCacheDir()
if dirErr == nil {
dir = filepath.Join(dir, "ExampleUserCacheDir")
}
getCache := func(name string) ([]byte, error) {
if dirErr != nil {
return nil, &os.PathError{Op: "getCache", Path: name, Err: os.ErrNotExist}
}
return os.ReadFile(filepath.Join(dir, name))
}
var mkdirOnce sync.Once
putCache := func(name string, b []byte) error {
if dirErr != nil {
return &os.PathError{Op: "putCache", Path: name, Err: dirErr}
}
mkdirOnce.Do(func() {
if err := os.MkdirAll(dir, 0700); err != nil {
log.Printf("can't create user cache dir: %v", err)
}
})
return os.WriteFile(filepath.Join(dir, name), b, 0600)
}
// Read and store cached data.
// …
_ = getCache
_ = putCache
}
func UserConfigDir
func UserConfigDir() (string, error)
UserConfigDir возвращает корневой каталог по умолчанию, который используется для данных конфигурации, специфичных для пользователя. Пользователи должны создать свой собственный подкаталог для конкретного приложения в этом каталоге и использовать его.
В системах Unix она возвращает $XDG_CONFIG_HOME, как указано в https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html, если он не пустой, в противном случае — $HOME/.config. В Darwin она возвращает $HOME/Library/Application Support. В Windows она возвращает %AppData%. В Plan 9 она возвращает $home/lib.
Если местоположение не может быть определено (например, $HOME не определено) или путь в $XDG_CONFIG_HOME является относительным, то будет возвращена ошибка.
Пример
package main
import (
"bytes"
"log"
"os"
"path/filepath"
)
func main() {
dir, dirErr := os.UserConfigDir()
var (
configPath string
origConfig []byte
)
if dirErr == nil {
configPath = filepath.Join(dir, "ExampleUserConfigDir", "example.conf")
var err error
origConfig, err = os.ReadFile(configPath)
if err != nil && !os.IsNotExist(err) {
// The user has a config file but we couldn't read it.
// Report the error instead of ignoring their configuration.
log.Fatal(err)
}
}
// Use and perhaps make changes to the config.
config := bytes.Clone(origConfig)
// …
// Save changes.
if !bytes.Equal(config, origConfig) {
if configPath == "" {
log.Printf("not saving config changes: %v", dirErr)
} else {
err := os.MkdirAll(filepath.Dir(configPath), 0700)
if err == nil {
err = os.WriteFile(configPath, config, 0600)
}
if err != nil {
log.Printf("error saving config changes: %v", err)
}
}
}
}
func UserHomeDir
func UserHomeDir() (string, error)
UserHomeDir возвращает домашний каталог текущего пользователя.
В Unix, включая macOS, возвращает переменную среды $HOME. В Windows возвращает %USERPROFILE%. В Plan 9 возвращает переменную среды $home.
Если ожидаемая переменная не задана в среде, UserHomeDir возвращает либо значение по умолчанию, специфичное для платформы, либо ошибку, отличную от nil.
func WriteFile
func WriteFile(name string, data []byte, perm FileMode) error
WriteFile записывает данные в файл с указанным именем, создавая его при необходимости. Если файл не существует, WriteFile создает его с правами доступа perm (до umask); в противном случае WriteFile обрезает его перед записью, не изменяя права доступа. Поскольку WriteFile требует нескольких системных вызовов для завершения, сбой в середине операции может оставить файл в частично записанном состоянии.
Пример
package main
import (
"log"
"os"
)
func main() {
err := os.WriteFile("testdata/hello", []byte("Hello, Gophers!"), 0666)
if err != nil {
log.Fatal(err)
}
}
11.2 - Описание типов пакета os языка программирования Go
type DirEntry
type DirEntry = fs.DirEntry
DirEntry — это запись, прочитанная из каталога (с помощью функции ReadDir или метода File.ReadDir).
func ReadDir
func ReadDir(name string) ([]DirEntry, error)
ReadDir считывает указанный каталог и возвращает все его записи, отсортированные по имени файла. Если при чтении каталога происходит ошибка, ReadDir возвращает записи, которые удалось прочитать до ошибки, вместе с ошибкой.
Пример
package main
import (
"fmt"
"log"
"os"
)
func main() {
files, err := os.ReadDir(".")
if err != nil {
log.Fatal(err)
}
for _, file := range files {
fmt.Println(file.Name())
}
}
type File
type File struct {
// содержит отфильтрованные или неэкспортируемые поля
}
File представляет открытый файловый дескриптор.
Методы File безопасны для одновременного использования.
func Create
func Create(name string) (*File, error)
Create создает или обрезает файл с указанным именем. Если файл уже существует, он обрезается. Если файл не существует, он создается с режимом 0o666 (до umask). В случае успеха методы возвращенного File могут использоваться для ввода-вывода; связанный файловый дескриптор имеет режим O_RDWR. Каталог, содержащий файл, должен уже существовать. Если произошла ошибка, она будет типа *PathError.
func CreateTemp
func CreateTemp(dir, pattern string) (*File, error)
CreateTemp создает новый временный файл в каталоге dir, открывает файл для чтения и записи и возвращает полученный файл. Имя файла генерируется путем добавления случайной строки в конец pattern. Если pattern содержит "*", случайная строка заменяет последний "*". Файл создается с режимом 0o600 (до umask). Если dir — пустая строка, CreateTemp использует каталог по умолчанию для временных файлов, возвращаемый TempDir. Несколько программ или goroutines, вызывающих CreateTemp одновременно, не будут выбирать один и тот же файл. Вызывающий может использовать метод Name файла, чтобы найти путь к файлу. Вызывающий несет ответственность за удаление файла, когда он больше не нужен.
Пример
package main
import (
"log"
"os"
)
func main() {
f, err := os.CreateTemp("", "example")
if err != nil {
log.Fatal(err)
}
defer os.Remove(f.Name()) // clean up
if _, err := f.Write([]byte("content")); err != nil {
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
}
Пример suffix
package main
import (
"log"
"os"
)
func main() {
f, err := os.CreateTemp("", "example.*.txt")
if err != nil {
log.Fatal(err)
}
defer os.Remove(f.Name()) // clean up
if _, err := f.Write([]byte("content")); err != nil {
f.Close()
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
}
func NewFile
func NewFile(fd uintptr, name string) *File
NewFile возвращает новый File с заданным файловым дескриптором и именем. Возвращаемое значение будет nil, если fd не является действительным файловым дескриптором. В системах Unix, если файловый дескриптор находится в неблокирующем режиме, NewFile попытается вернуть опрашиваемый File (такой, для которого работают методы SetDeadline).
После передачи в NewFile fd может стать недействительным при тех же условиях, что описаны в комментариях к методу Fd, и применяются те же ограничения.
func Open
func Open(name string) (*File, error)
Open открывает указанный файл для чтения. В случае успеха методы возвращенного файла могут быть использованы для чтения; связанный файловый дескриптор имеет режим O_RDONLY. Если произошла ошибка, она будет типа *PathError.
func OpenFile
func OpenFile(name string, flag int, perm FileMode) (*File, error)
OpenFile — это обобщенный вызов open; большинство пользователей вместо него будут использовать Open или Create. Он открывает файл с указанным флагом (O_RDONLY и т. д.). Если файл не существует и передан флаг O_CREATE, он создается с режимом perm (до umask); содержащий его каталог должен существовать. В случае успеха методы возвращенного File можно использовать для ввода-вывода. Если произошла ошибка, она будет типа *PathError.
Пример
package main
import (
"log"
"os"
)
func main() {
f, err := os.OpenFile("notes.txt", os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
}
Пример добавление
package main
import (
"log"
"os"
)
func main() {
// If the file doesn't exist, create it, or append to the file
f, err := os.OpenFile("access.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
if _, err := f.Write([]byte("appended some data\n")); err != nil {
f.Close() // ignore error; Write error takes precedence
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
}
func OpenInRoot
func OpenInRoot(dir, name string) (*File, error)
OpenInRoot открывает файл name в каталоге dir. Это эквивалентно OpenRoot(dir) с последующим открытием файла в корневом каталоге.
OpenInRoot возвращает ошибку, если какой-либо компонент имени name ссылается на местоположение за пределами dir.
См. Root для получения подробной информации и ограничений.
func (*File) Chdir
func (f *File) Chdir() error
Chdir изменяет текущий рабочий каталог на файл, который должен быть каталогом. Если произошла ошибка, она будет типа *PathError.
func (*File) Chmod
func (f *File) Chmod(mode FileMode) error
Chmod изменяет режим файла на mode. Если произошла ошибка, она будет типа *PathError.
func (*File) Chown
func (f *File) Chown(uid, gid int) error
Chown изменяет числовые значения uid и gid указанного файла. Если произошла ошибка, она будет типа *PathError.
В Windows всегда возвращается ошибка syscall.EWINDOWS, обернутая в *PathError.
func (*File) Close
func (f *File) Close() error
Close закрывает файл, делая его недоступным для ввода-вывода. Для файлов, поддерживающих File.SetDeadline, все ожидающие операции ввода-вывода будут отменены и немедленно возвращены с ошибкой ErrClosed. Close вернет ошибку, если он уже был вызван.
func (*File) Fd
func (f *File) Fd() uintptr
Fd возвращает целое число Unix-дескриптора файла, ссылающегося на открытый файл. Если f закрыт, дескриптор файла становится недействительным. Если f подвергается сборке мусора, финализатор может закрыть дескриптор файла, сделав его недействительным; см. runtime.SetFinalizer для получения дополнительной информации о том, когда может быть запущен финализатор. В системах Unix это приведет к прекращению работы методов File.SetDeadline. Поскольку файловые дескрипторы могут быть повторно использованы, возвращенный файловый дескриптор может быть закрыт только с помощью метода File.Close f или его финализатором во время сборки мусора. В противном случае во время сборки мусора финализатор может закрыть не связанный файловый дескриптор с тем же (повторно используемым) номером.
В качестве альтернативы см. метод f.SyscallConn.
func (*File) Name
func (f *File) Name() string
Name возвращает имя файла, представленное в Open.
Безопасно вызывать Name после [Close].
func (*File) Read
func (f *File) Read(b []byte) (n int, err error)
Read считывает до len(b) байтов из File и сохраняет их в b. Он возвращает количество прочитанных байтов и любую возникшую ошибку. В конце файла Read возвращает 0, io.EOF.
func (*File) ReadAt
func (f *File) ReadAt(b []byte, off int64) (n int, err error)
ReadAt считывает len(b) байт из файла, начиная с байтового смещения off. Она возвращает количество прочитанных байтов и ошибку, если таковая имеется. ReadAt всегда возвращает ошибку, отличную от nil, когда n < len(b). В конце файла эта ошибка равна io.EOF.
func (*File) ReadDir
func (f *File) ReadDir(n int) ([]DirEntry, error)
ReadDir считывает содержимое каталога, связанного с файлом f, и возвращает массив значений DirEntry в порядке каталога. Последующие вызовы для того же файла будут возвращать последующие записи DirEntry в каталоге.
Если n > 0, ReadDir возвращает не более n записей DirEntry. В этом случае, если ReadDir возвращает пустой срез, он вернет ошибку с объяснением причины. В конце каталога ошибкой является io.EOF.
Если n <= 0, ReadDir возвращает все записи DirEntry, оставшиеся в каталоге. При успешном выполнении он возвращает ошибку nil (не io.EOF).
func (*File) ReadFrom
func (f *File) ReadFrom(r io.Reader) (n int64, err error)
ReadFrom реализует io.ReaderFrom.
func (*File) Readdir
func (f *File) Readdir(n int) ([]FileInfo, error)
Readdir считывает содержимое каталога, связанного с файлом, и возвращает массив из n значений FileInfo, как это было бы возвращено Lstat, в порядке каталога. Последующие вызовы для того же файла будут возвращать дополнительные FileInfos.
Если n > 0, Readdir возвращает не более n структур FileInfo. В этом случае, если Readdir возвращает пустой срез, он вернет не нулевую ошибку с объяснением причины. В конце каталога ошибкой является io.EOF.
Если n <= 0, Readdir возвращает все FileInfo из каталога в одном срезе. В этом случае, если Readdir выполняется успешно (считывает до конца каталога), он возвращает срез и ошибку nil. Если он встречает ошибку до конца каталога, Readdir возвращает FileInfo, прочитанные до этого момента, и ошибку, отличную от nil.
Большинству клиентов лучше подходит более эффективный метод ReadDir.
func (*File) Readdirnames
func (f *File) Readdirnames(n int) (names []string, err error)
Readdirnames считывает содержимое каталога, связанного с файлом, и возвращает срез из n имен файлов в каталоге в порядке их расположения в каталоге. Последующие вызовы для того же файла будут возвращать дальнейшие имена.
Если n > 0, Readdirnames возвращает не более n имен. В этом случае, если Readdirnames возвращает пустой срез, он возвращает не нулевую ошибку с объяснением причины. В конце каталога ошибкой является io.EOF.
Если n <= 0, Readdirnames возвращает все имена из каталога в одном фрагменте. В этом случае, если Readdirnames выполняется успешно (считывает до конца каталога), он возвращает фрагмент и ошибку nil. Если перед концом каталога возникает ошибка, Readdirnames возвращает имена, прочитанные до этого момента, и ошибку, отличную от nil.
func (*File) Seek
func (f *File) Seek(offset int64, whence int) (ret int64, err error)
Seek устанавливает смещение для следующего чтения или записи в файл, интерпретируемое в соответствии с whence: 0 означает относительно начала файла, 1 означает относительно текущего смещения, а 2 означает относительно конца. Возвращает новое смещение и ошибку, если она есть. Поведение Seek для файла, открытого с O_APPEND, не определено.
func (*File) SetDeadline
func (f *File) SetDeadline(t time.Time) error
SetDeadline устанавливает сроки чтения и записи для файла. Это эквивалентно вызову SetReadDeadline и SetWriteDeadline.
Только некоторые типы файлов поддерживают установку срока. Вызов SetDeadline для файлов, которые не поддерживают сроки, вернет ErrNoDeadline. В большинстве систем обычные файлы не поддерживают сроки, но каналы поддерживают.
Срок — это абсолютное время, по истечении которого операции ввода-вывода завершаются с ошибкой, а не блокируются. Срок применяется ко всем будущим и ожидающим операциям ввода-вывода, а не только к следующему вызову Read или Write. После превышения срока соединение можно обновить, установив срок в будущем.
Если срок превышен, вызов Read или Write или других методов ввода-вывода вернет ошибку, которая оборачивает ErrDeadlineExceeded. Это можно проверить с помощью errors.Is(err, os.ErrDeadlineExceeded). Эта ошибка реализует метод Timeout, и вызов метода Timeout вернет true, но есть и другие возможные ошибки, для которых Timeout вернет true, даже если срок не был превышен.
Таймаут простоя можно реализовать путем многократного продления срока после успешных вызовов Read или Write.
Нулевое значение t означает, что операции ввода-вывода не будут прерываться по истечении таймаута.
func (*File) SetReadDeadline
func (f *File) SetReadDeadline(t time.Time) error
SetReadDeadline устанавливает срок для будущих вызовов Read и любых заблокированных в данный момент вызовов Read. Нулевое значение t означает, что Read не будет прерываться по истечении времени ожидания. Не все файлы поддерживают установку сроков; см. SetDeadline.
func (*File) SetWriteDeadline
func (f *File) SetWriteDeadline(t time.Time) error
SetWriteDeadline устанавливает срок для любых будущих вызовов Write и любых заблокированных в данный момент вызовов Write. Даже если Write превышает время ожидания, он может вернуть n > 0, указывая, что часть данных была успешно записана. Нулевое значение t означает, что Write не превысит время ожидания. Не все файлы поддерживают установку сроков; см. SetDeadline.
func (*File) Stat
func (f *File) Stat() (FileInfo, error)
Stat возвращает структуру FileInfo, описывающую файл. Если произошла ошибка, она будет иметь тип *PathError.
func (*File) Sync
func (f *File) Sync() error
Sync фиксирует текущее содержимое файла в стабильном хранилище. Обычно это означает сброс в память файловой системы копии недавно записанных данных на диск.
func (*File) SyscallConn
func (f *File) SyscallConn() (syscall.RawConn, error)
SyscallConn возвращает необработанный файл. Это реализует интерфейс syscall.Conn.
func (*File) Truncate
func (f *File) Truncate(size int64) error
Truncate изменяет размер файла. Он не изменяет смещение ввода-вывода. Если происходит ошибка, она будет типа *PathError.
func (*File) Write
func (f *File) Write(b []byte) (n int, err error)
Write записывает len(b) байт из b в File. Он возвращает количество записанных байт и ошибку, если она есть. Write возвращает ошибку, отличную от nil, когда n != len(b).
func (*File) WriteAt
func (f *File) WriteAt(b []byte, off int64) (n int, err error)
WriteAt записывает len(b) байт в файл, начиная с байтового смещения off. Он возвращает количество записанных байтов и ошибку, если она есть. WriteAt возвращает ошибку, отличную от nil, когда n != len(b).
Если файл был открыт с флагом O_APPEND, WriteAt возвращает ошибку.
func (*File) WriteString
func (f *File) WriteString(s string) (n int, err error)
WriteString похож на Write, но записывает содержимое строки s, а не фрагмент байтов.
func (*File) WriteTo
func (f *File) WriteTo(w io.Writer) (n int64, err error)
WriteTo реализует io.WriterTo.
type FileInfo
type FileInfo = fs.FileInfo
FileInfo описывает файл и возвращается Stat и Lstat.
func Lstat
func Lstat(name string) (FileInfo, error)
Lstat возвращает FileInfo, описывающий файл с указанным именем. Если файл является символической ссылкой, возвращаемый FileInfo описывает символическую ссылку. Lstat не пытается следовать по ссылке. Если происходит ошибка, она будет типа *PathError.
В Windows, если файл является точкой повторного анализа, которая является заменителем другого именованного объекта (например, символической ссылки или смонтированной папки), возвращаемый FileInfo описывает точку повторного анализа и не пытается ее разрешить.
func Stat
func Stat(name string) (FileInfo, error)
Stat возвращает FileInfo, описывающий именованный файл. Если произошла ошибка, она будет типа *PathError.
type FileMode
type FileMode = fs.FileMode
FileMode представляет режим файла и биты разрешений. Биты имеют одинаковое определение во всех системах, поэтому информация о файлах может быть перенесена из одной системы в другую. Не все биты применимы ко всем системам. Единственный обязательный бит — ModeDir для каталогов.
Пример ¶
type LinkError
type LinkError struct {
Op string
Old string
New string
Err error
}
LinkError записывает ошибку, возникшую во время системного вызова link, symlink или rename, а также пути, которые ее вызвали.
func (*LinkError) Error
func (e *LinkError) Error() string
func (*LinkError) Unwrap
func (e *LinkError) Unwrap() error
type PathError
type PathError = fs.PathError
PathError регистрирует ошибку, а также операцию и путь к файлу, которые ее вызвали.
type ProcAttr
type ProcAttr struct {
// Если Dir не пустой, дочерний процесс переходит в этот каталог перед
// созданием процесса.
Dir string
// Если Env не равно nil, оно возвращает переменные окружения для
// нового процесса в форме, возвращаемой Environ.
// Если оно равно nil, будет использован результат Environ.
Env []string
// Files указывает открытые файлы, унаследованные новым процессом.
// Первые три записи соответствуют стандартному вводу, стандартному выводу и
// стандартной ошибке. Реализация может поддерживать дополнительные записи,
// в зависимости от базовой операционной системы. Запись nil соответствует
// закрытию этого файла при запуске процесса.
// В системах Unix StartProcess изменит эти значения File
// в режим блокировки, что означает, что SetDeadline перестанет работать,
// а вызов Close не прервет Read или Write.
Files []*File
// Атрибуты создания процесса, специфичные для операционной системы.
// Обратите внимание, что установка этого поля означает, что ваша программа
// может не работать должным образом или даже не компилироваться в некоторых
// операционных системах.
Sys *syscall.SysProcAttr
}
ProcAttr содержит атрибуты, которые будут применены к новому процессу, запущенному StartProcess.
type Process
type Process struct {
Pid int
// содержит отфильтрованные или неэкспортируемые поля
}
Process хранит информацию о процессе, созданном StartProcess.
func FindProcess
func FindProcess(pid int) (*Process, error)
FindProcess ищет запущенный процесс по его pid.
Возвращаемый им Process можно использовать для получения информации о процессе базовой операционной системы.
В системах Unix FindProcess всегда выполняется успешно и возвращает Process для заданного pid, независимо от того, существует ли процесс. Чтобы проверить, действительно ли процесс существует, посмотрите, сообщает ли p.Signal(syscall.Signal(0)) об ошибке.
func StartProcess
func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error)
StartProcess запускает новый процесс с программой, аргументами и атрибутами, указанными в name, argv и attr. Срез argv станет os.Args в новом процессе, поэтому он обычно начинается с имени программы.
Если вызывающая goroutine заблокировала поток операционной системы с помощью runtime.LockOSThread и изменила любое наследуемое состояние потока на уровне ОС (например, пространства имен Linux или Plan 9), новый процесс унаследует состояние потока вызывающего.
StartProcess является низкоуровневым интерфейсом. Пакет os/exec предоставляет интерфейсы более высокого уровня.
Если произошла ошибка, она будет иметь тип *PathError.
func (*Process) Kill
func (p *Process) Kill() error
Kill вызывает немедленный выход Process. Kill не ждет, пока Process действительно завершится. Это убивает только сам Process, а не другие процессы, которые он мог запустить.
func (*Process) Release
func (p *Process) Release() error
Release освобождает все ресурсы, связанные с процессом p, делая его непригодным для использования в будущем. Release нужно вызывать только в том случае, если Process.Wait не вызывается.
func (*Process) Signal
func (p *Process) Signal(sig Signal) error
Signal посылает сигнал процессу. Отправка Interrupt в Windows не реализована.
func (*Process) Wait
func (p *Process) Wait() (*ProcessState, error)
Wait ожидает завершения процесса, а затем возвращает ProcessState, описывающий его состояние, и ошибку, если она есть. Wait освобождает все ресурсы, связанные с процессом. В большинстве операционных систем процесс должен быть дочерним по отношению к текущему процессу, иначе будет возвращена ошибка.
type ProcessState
type ProcessState struct {
// содержит отфильтрованные или неэкспортируемые поля
}
ProcessState хранит информацию о процессе, как сообщается Wait.
func (*ProcessState) ExitCode
func (p *ProcessState) ExitCode() int
ExitCode возвращает код завершения завершившегося процесса или -1, если процесс не завершился или был прерван сигналом.
func (*ProcessState) Exited
func (p *ProcessState) Exited() bool
Exited сообщает, завершилась ли программа. В системах Unix это возвращает true, если программа завершилась из-за вызова exit, но false, если программа была прервана сигналом.
func (*ProcessState) Pid
func (p *ProcessState) Pid() int
Pid возвращает идентификатор завершившегося процесса.
func (*ProcessState) String
func (p *ProcessState) String() string
func (*ProcessState) Success
func (p *ProcessState) Success() bool
Success сообщает, завершилась ли программа успешно, например, со статусом завершения 0 в Unix.
func (*ProcessState) Sys
func (p *ProcessState) Sys() any
Sys возвращает системную информацию о завершении процесса. Преобразуйте ее в соответствующий базовый тип, например syscall.WaitStatus в Unix, чтобы получить доступ к ее содержимому.
func (*ProcessState) SysUsage
func (p *ProcessState) SysUsage() any
SysUsage возвращает системную информацию об использовании ресурсов завершенного процесса. Преобразуйте ее в соответствующий базовый тип, например *syscall.Rusage в Unix, чтобы получить доступ к ее содержимому. (В Unix *syscall.Rusage соответствует struct rusage, как определено в справочной странице getrusage(2).)
func (*ProcessState) SystemTime
func (p *ProcessState) SystemTime() time.Duration
SystemTime возвращает системное время ЦП завершенного процесса и его дочерних процессов.
func (*ProcessState) UserTime
func (p *ProcessState) UserTime() time.Duration
UserTime возвращает пользовательское время ЦП завершенного процесса и его дочерних процессов.
type Root
type Root struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Root может использоваться только для доступа к файлам в пределах одного дерева каталогов.
Методы Root могут обращаться только к файлам и каталогам, расположенным ниже корневого каталога. Если какой-либо компонент имени файла, переданного методу Root, ссылается на местоположение за пределами корня, метод возвращает ошибку. Имена файлов могут ссылаться на сам каталог (.).
Методы Root будут следовать символьным ссылкам, но символьные ссылки не могут ссылаться на местоположение за пределами корня. Символьные ссылки не должны быть абсолютными.
Методы Root не запрещают переход через границы файловой системы, монтирование Linux, специальные файлы /proc или доступ к файлам устройств Unix.
Методы Root можно безопасно использовать одновременно из нескольких goroutines.
На большинстве платформ создание Root открывает файловый дескриптор или дескриптор, ссылающийся на каталог. Если каталог перемещен, методы Root ссылаются на исходный каталог в его новом местоположении.
Поведение Root отличается на некоторых платформах:
- Когда GOOS=windows, имена файлов не могут ссылаться на зарезервированные Windows имена устройств, такие как NUL и COM1.
- Когда GOOS=js, Root уязвим для атак TOCTOU (time-of-check-time-of-use) при проверке символьных ссылок и не может гарантировать, что операции не выйдут за пределы корня.
- Когда GOOS=plan9 или GOOS=js, Root не отслеживает каталоги при переименовании. На этих платформах Root ссылается на имя каталога, а не на файловый дескриптор.
func OpenRoot
func OpenRoot(name string) (*Root, error)
OpenRoot открывает указанный каталог. Если произошла ошибка, она будет типа *PathError.
func (*Root) Close
func (r *Root) Close() error
Close закрывает Root. После вызова Close методы Root возвращают ошибки.
func (*Root) Create
func (r *Root) Create(name string) (*File, error)
Create создает или обрезает указанный файл в корне. Подробнее см. Create.
func (*Root) FS
func (r *Root) FS() fs.FS
FS возвращает файловую систему (fs.FS) для дерева файлов в корне.
Результат реализует io/fs.StatFS, io/fs.ReadFileFS и io/fs.ReadDirFS.
func (*Root) Lstat
func (r *Root) Lstat(name string) (FileInfo, error)
Lstat возвращает FileInfo, описывающий указанный файл в корневом каталоге. Если файл является символической ссылкой, возвращаемый FileInfo описывает символическую ссылку. Подробнее см. Lstat.
func (*Root) Mkdir
func (r *Root) Mkdir(name string, perm FileMode) error
Mkdir создает новый каталог в корне с указанным именем и битами разрешений (до umask). Подробнее см. Mkdir.
Если perm содержит биты, отличные от девяти младших битов (0o777), OpenFile возвращает ошибку.
func (*Root) Name
func (r *Root) Name() string
Name возвращает имя каталога, представленного OpenRoot.
Безопасно вызывать Name после [Close].
func (*Root) Open
func (r *Root) Open(name string) (*File, error)
Open открывает указанный файл в корне для чтения. Подробнее см. Open.
func (*Root) OpenFile
func (r *Root) OpenFile(name string, flag int, perm FileMode) (*File, error)
OpenFile открывает указанный файл в корне. Подробнее см. OpenFile.
Если perm содержит биты, отличные от девяти младших битов (0o777), OpenFile возвращает ошибку.
func (*Root) OpenRoot
func (r *Root) OpenRoot(name string) (*Root, error)
OpenRoot открывает указанный каталог в корневом каталоге. Если произошла ошибка, она будет типа *PathError.
func (*Root) Remove
func (r *Root) Remove(name string) error
Remove удаляет указанный файл или (пустой) каталог в корневом каталоге. Подробнее см. в разделе Remove.
func (*Root) Stat
func (r *Root) Stat(name string) (FileInfo, error)
Stat возвращает FileInfo, описывающий файл с указанным именем в корне. Подробнее см. Stat.
type Signal
type Signal interface {
String() string
Signal() // для отличия от других Stringers
}
Signal представляет сигнал операционной системы. Обычная базовая реализация зависит от операционной системы: в Unix это syscall.Signal.
var (
Interrupt Signal = syscall.SIGINT
Kill Signal = syscall.SIGKILL)
Единственные значения сигналов, которые гарантированно присутствуют в пакете os на всех системах, — это os.Interrupt (отправить процессу прерывание) и os.Kill (принудительно завершить процесс). В Windows отправка os.Interrupt процессу с помощью os.Process.Signal не реализована; вместо отправки сигнала будет возвращена ошибка.
type SyscallError
type SyscallError struct {
Syscall string
Err error
}
SyscallError регистрирует ошибку от конкретного системного вызова.
func (*SyscallError) Error
func (e *SyscallError) Error() string
func (*SyscallError) Timeout
func (e *SyscallError) Timeout() bool
Timeout сообщает, является ли эта ошибка тайм-аутом.
func (*SyscallError) Unwrap
func (e *SyscallError) Unwrap() error
11.3 - Описание функций и типов пакета os/exec языка программирования Go
В отличие от вызова библиотеки «system» из C и других языков, пакет os/exec намеренно не вызывает системную оболочку и не расширяет никакие шаблоны glob, а также не обрабатывает другие расширения, конвейеры или перенаправления, которые обычно выполняются оболочками. Пакет ведет себя больше как семейство функций «exec» в C. Чтобы расширить шаблоны glob, либо вызовите оболочку напрямую, позаботившись об экранировании опасных входных данных, либо используйте функцию Glob из пакета path/filepath. Для расширения переменных окружения используйте ExpandEnv из пакета os.
Обратите внимание, что примеры в этом пакете предполагают использование системы Unix. Они могут не работать в Windows и не работают в Go Playground, используемом golang.org и godoc.org.
Исполняемые файлы в текущем каталоге
Функции Command и LookPath ищут программу в каталогах, перечисленных в текущем пути, следуя соглашениям операционной системы хоста. На протяжении десятилетий операционные системы включали текущий каталог в этот поиск, иногда неявно, а иногда явно настраивая его таким образом по умолчанию. Современная практика такова, что включение текущего каталога обычно является неожиданным и часто приводит к проблемам безопасности.
Чтобы избежать этих проблем безопасности, начиная с Go 1.19, этот пакет не будет разрешать программу, используя явную или неявную запись пути относительно текущего каталога. То есть, если вы запустите LookPath(“go”), он не вернет ./go в Unix и .\go.exe в Windows, независимо от того, как настроен путь. Вместо этого, если обычные алгоритмы пути приведут к такому ответу, эти функции возвращают ошибку err, удовлетворяющую errors.Is(err, ErrDot).
Рассмотрим, например, эти два фрагмента программы:
path, err := exec.LookPath("prog")
if err != nil {
log.Fatal(err)
}
use(path)
cmd := exec.Command("prog")
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
Они не найдут и не запустят ./prog или .\prog.exe, независимо от того, как настроен текущий путь.
Код, который всегда хочет запускать программу из текущего каталога, можно переписать так, чтобы вместо “prog” он говорил “./prog”.
Код, который настаивает на включении результатов из записей относительных путей, может вместо этого отменить ошибку с помощью проверки errors.Is:
path, err := exec.LookPath("prog")
if errors.Is(err, exec.ErrDot) {
err = nil
}
if err != nil {
log.Fatal(err)
}
use(path)
cmd := exec.Command("prog")
if errors.Is(cmd.Err, exec.ErrDot) {
cmd.Err = nil
}
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
Установка переменной окружения GODEBUG=execerrdot=0 полностью отключает генерацию ErrDot, временно восстанавливая поведение, существовавшее до Go 1.19, для программ, которые не могут применить более целевые исправления. В будущих версиях Go поддержка этой переменной может быть удалена.
Прежде чем добавлять такие переопределения, убедитесь, что вы понимаете последствия этого для безопасности. Дополнительные сведения см. на сайте https://go.dev/blog/path-security.
Переменные
var ErrDot = errors.New("cannot run executable found relative to current directory")
ErrDot указывает, что поиск пути привел к появлению исполняемого файла в текущем каталоге из-за ‘.’ в пути, либо неявно, либо явно. Подробности см. в документации к пакету.
Обратите внимание, что функции этого пакета не возвращают ErrDot напрямую. Для проверки того, связана ли возвращаемая ошибка err с этим условием, в коде следует использовать errors.Is(err, ErrDot), а не err == ErrDot.
var ErrNotFound = errors.New("исполняемый файл не найден в $PATH")
ErrNotFound - это ошибка, возникающая, если при поиске по пути не удалось найти исполняемый файл.
var ErrWaitDelay = errors.New("exec: WaitDelay expired before I/O complete")
ErrWaitDelay возвращается Cmd.Wait, если процесс завершается с успешным кодом состояния, но его выходные трубы не закрыты до истечения WaitDelay команды.
Функции
func LookPath
func LookPath(file string) (string, error)
LookPath ищет исполняемый файл с именем file в каталогах, указанных переменной окружения PATH. Если файл содержит косую черту, то поиск ведется напрямую и PATH не используется. В противном случае, при успехе, результатом будет абсолютный путь.
В старых версиях Go LookPath мог возвращать путь относительно текущего каталога. Начиная с Go 1.19, LookPath будет возвращать этот путь вместе с ошибкой, удовлетворяющей errors.Is(err, ErrDot). Более подробную информацию см. в документации к пакету.
Пример
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
path, err := exec.LookPath("fortune")
if err != nil {
log.Fatal("installing fortune is in your future")
}
fmt.Printf("fortune is available at %s\n", path)
}
Типы
type Cmd
type Cmd struct {
// Path — путь к выполняемой команде.
//
// Это единственное поле, которое должно иметь значение, отличное от нуля.
// Если Path является относительным, оно оценивается относительно
// Dir.
Path string
// Args содержит аргументы командной строки, включая команду как Args[0].
// Если поле Args пустое или равно nil, Run использует {Path}.
//
// В типичном случае Path и Args задаются вызовом Command.
Args []string
// Env задает среду процесса.
// Каждая запись имеет вид «ключ=значение».
// Если Env равно nil, новый процесс использует среду текущего процесса.
//
// Если Env содержит дубликаты ключей среды, используется только последнее
// значение в срезе для каждого дубликата ключа.
// В качестве особого случая в Windows SYSTEMROOT всегда добавляется, если
// отсутствует и явно не установлен в пустую строку.
//
// См. также поле Dir, которое может устанавливать PWD в среде.
Env []string
// Dir указывает рабочий каталог команды.
// Если Dir является пустой строкой, Run запускает команду в
// текущем каталоге вызывающего процесса.
//
// В системах Unix значение Dir также определяет
// переменную окружения PWD дочернего процесса, если не указано иное
//. Процесс Unix представляет свой рабочий каталог
// не по имени, а как неявную ссылку на узел в
// дереве файлов. Таким образом, если дочерний процесс получает свой рабочий
// каталог путем вызова функции, такой как getcwd в C, которая
// вычисляет каноническое имя, проходя по дереву файлов, он
// не восстановит исходное значение Dir, если это значение
// было псевдонимом, включающим символьные ссылки. Однако, если
// дочерний процесс вызывает [os.Getwd] в Go или
// get_current_dir_name из GNU C, и значение PWD является псевдонимом для
// текущего каталога, эти функции вернут
// значение PWD, которое совпадает со значением Dir.
Dir string
// Stdin указывает стандартный ввод процесса.
//
// Если Stdin равно nil, процесс читает из нулевого устройства (os.DevNull).
//
// Если Stdin является *os.File, стандартный ввод процесса подключается
// непосредственно к этому файлу.
//
// В противном случае во время выполнения команды отдельная
// goroutine считывает данные из Stdin и передает их команде
// через канал. В этом случае Wait не завершается, пока goroutine
// не прекратит копирование, либо потому что достиг конца Stdin
// (EOF или ошибка чтения), либо потому, что запись в канал вернула ошибку,
// либо потому, что было установлено ненулевое значение WaitDelay, которое истекло.
Stdin io.Reader
// Stdout и Stderr указывают стандартный вывод и ошибки процесса.
//
// Если любой из них равен nil, Run подключает соответствующий файловый дескриптор
// к нулевому устройству (os.DevNull).
//
// Если любой из них является *os.File, соответствующий вывод из процесса
// подключается непосредственно к этому файлу.
//
// В противном случае во время выполнения команды отдельная goroutine
// считывает данные из процесса через канал и доставляет их в
// соответствующий Writer. В этом случае Wait не завершается, пока
// goroutine не достигнет EOF, не столкнется с ошибкой или не истечет ненулевое значение WaitDelay
//.
//
// Если Stdout и Stderr являются одним и тем же writer и имеют тип, который можно
// сравнить с помощью ==, то одновременно Write будет вызывать не более одного goroutine.
Stdout io.Writer
Stderr io.Writer
// ExtraFiles указывает дополнительные открытые файлы, которые будут унаследованы
// новый процесс. Он не включает стандартный ввод, стандартный вывод или
// стандартную ошибку. Если не равен nil, запись i становится файловым дескриптором 3+i.
//
// ExtraFiles не поддерживается в Windows.
ExtraFiles []*os.File
// SysProcAttr содержит дополнительные атрибуты, специфичные для операционной системы.
// Run передает его os.StartProcess как поле Sys os.ProcAttr.
SysProcAttr *syscall.SysProcAttr
// Process — это базовый процесс после запуска.
Process *os.Process
// ProcessState содержит информацию о завершенном процессе.
// Если процесс был запущен успешно, Wait или Run
// заполнят его ProcessState по завершении команды.
ProcessState *os.ProcessState
Err error // Ошибка LookPath, если есть.
// Если Cancel не равен nil, команда должна быть создана с помощью
// CommandContext, и Cancel будет вызван, когда
// Context команды будет выполнен. По умолчанию CommandContext устанавливает Cancel в
// вызывает метод Kill в процессе команды.
//
// Обычно пользовательский Cancel посылает сигнал процессу команды
//, но вместо этого он может предпринять другие действия для инициирования отмены,
// такие как закрытие канала stdin или stdout или отправка запроса на завершение работы
// сетевого сокета.
//
// Если команда завершается с успешным статусом после вызова Cancel
//, а Cancel не возвращает ошибку, эквивалентную
// os.ErrProcessDone, то Wait и подобные методы будут возвращать не нулевую
// ошибку: либо ошибку, оборачивающую ошибку, возвращенную Cancel,
// либо ошибку из Context.
// (Если команда завершается с неуспешным статусом или Cancel
// возвращает ошибку, которая оборачивает os.ErrProcessDone, Wait и подобные методы
// продолжают возвращать обычный статус завершения команды.)
//
// Если Cancel установлен в nil, ничего не произойдет сразу после завершения
// Context команды, но WaitDelay, отличное от нуля, все равно будет действовать. Это может
// быть полезно, например, для обхода тупиковых ситуаций в командах, которые не
// поддерживают сигналы завершения, но должны всегда завершаться быстро.
//
// Cancel не будет вызван, если Start возвращает ошибку, отличную от nil.
Cancel func() error
// Если WaitDelay отлично от нуля, оно ограничивает время ожидания двух источников
// непредвиденной задержки в Wait: дочернего процесса, который не завершается после
// отмены связанного Context, и дочернего процесса, который завершается, но оставляет
// свои каналы ввода-вывода незакрытыми.
//
// Таймер WaitDelay запускается, когда связанный контекст завершается или
// вызов Wait обнаруживает, что дочерний процесс завершился, в зависимости от того, что произойдет
// первым. По истечении задержки команда завершает дочерний процесс
// и/или его каналы ввода-вывода.
//
// Если дочерний процесс не завершился — возможно, потому что он проигнорировал или
// не получил сигнал о завершении от функции Cancel, или потому что не была
// установлена функция Cancel — то он будет завершен с помощью os.Process.Kill.
//
// Затем, если каналы ввода-вывода, связывающиеся с дочерним процессом, все еще открыты,
// эти каналы закрываются, чтобы разблокировать все goroutines, которые в данный момент заблокированы
// вызовами Read или Write.
//
// Если трубы закрыты из-за WaitDelay, вызов Cancel не произошел,
// и команда завершилась с успешным статусом, Wait и
// подобные методы вернут ErrWaitDelay вместо nil.
//
// Если WaitDelay равен нулю (по умолчанию), трубы ввода-вывода будут читаться до EOF,
// что может не произойти, пока осиротевшие подпроцессы команды
// также не закроют свои дескрипторы для каналов.
WaitDelay time.Duration
// содержит отфильтрованные или неэкспортированные поля
}
Cmd представляет внешнюю команду, которая готовится или запускается.
Cmd не может быть повторно использован после вызова методов Cmd.Run, Cmd.Output или Cmd.CombinedOutput.
func Command
func Command(name string, arg ...string) *Cmd
Command возвращает структуру Cmd для выполнения именованной программы с заданными аргументами.
В возвращаемой структуре задаются только Path и Args.
Если имя не содержит разделителей путей, Command использует LookPath для преобразования имени в полный путь, если это возможно. В противном случае она использует имя непосредственно в качестве Path.
Возвращаемое поле Args Cmd строится из имени команды, за которым следуют элементы arg, поэтому arg не должно включать само имя команды. Например, Command(“echo”, “hello”). Args[0] - это всегда имя, а не возможно разрешенный Path.
В Windows процессы получают всю командную строку как единую строку и выполняют собственный разбор. Command объединяет и заключает Args в кавычки в строку командной строки с помощью алгоритма, совместимого с приложениями, использующими CommandLineToArgvW (это наиболее распространенный способ). Заметными исключениями являются msiexec.exe и cmd.exe (и, соответственно, все пакетные файлы), которые имеют другой алгоритм снятия кавычек. В этих и других подобных случаях вы можете выполнить котирование самостоятельно и указать полную командную строку в SysProcAttr.CmdLine, оставив Args пустым.
Пример
package main
import (
"fmt"
"log"
"os/exec"
"strings"
)
func main() {
cmd := exec.Command("tr", "a-z", "A-Z")
cmd.Stdin = strings.NewReader("some input")
var out strings.Builder
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
fmt.Printf("in all caps: %q\n", out.String())
}
Пример с окружением среды
package main
import (
"log"
"os"
"os/exec"
)
func main() {
cmd := exec.Command("prog")
cmd.Env = append(os.Environ(),
"FOO=duplicate_value", // ignored
"FOO=actual_value", // this value is used
)
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}
func CommandContext
func CommandContext(ctx context.Context, name string, arg ...string) *Cmd
CommandContext похож на Command, но включает в себя контекст.
Предоставленный контекст используется для прерывания процесса (путем вызова cmd.Cancel или os.Process.Kill), если контекст завершается до того, как команда завершит свою работу.
CommandContext устанавливает функцию Cancel команды для вызова метода Kill в ее Process и оставляет WaitDelay не установленным. Вызывающая сторона может изменить поведение отмены, изменив эти поля перед запуском команды.
Пример
package main
import (
"context"
"os/exec"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil {
// This will fail after 100 milliseconds. The 5 second sleep
// will be interrupted.
}
}
func (*Cmd) CombinedOutput
func (c *Cmd) CombinedOutput() ([]byte, error)
CombinedOutput запускает команду и возвращает ее объединенный стандартный вывод и стандартную ошибку.
Пример
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("sh", "-c", "echo stdout; echo 1>&2 stderr")
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", stdoutStderr)
}
func (*Cmd) Environ
func (c *Cmd) Environ() []string
Environ возвращает копию среды, в которой будет запущена команда, в том виде, в котором она настроена в данный момент.
Пример
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("pwd")
// Set Dir before calling cmd.Environ so that it will include an
// updated PWD variable (on platforms where that is used).
cmd.Dir = ".."
cmd.Env = append(cmd.Environ(), "POSIXLY_CORRECT=1")
out, err := cmd.Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", out)
}
func (*Cmd) Output
func (c *Cmd) Output() ([]byte, error)
Output запускает команду и возвращает ее стандартный вывод. Любая возвращаемая ошибка обычно будет иметь тип *ExitError. Если c.Stderr было nil, а возвращаемая ошибка имеет тип *ExitError, Output заполняет поле Stderr возвращаемой ошибки.
Пример
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
out, err := exec.Command("date").Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("The date is %s\n", out)
}
func (*Cmd) Run
func (c *Cmd) Run() error
Run запускает указанную команду и ждет ее завершения.
Возвращаемая ошибка равна nil, если команда запущена, не возникло проблем с копированием stdin, stdout и stderr, и она завершилась с нулевым статусом выхода.
Если команда запущена, но не завершилась успешно, ошибка будет типа *ExitError. В других ситуациях могут возвращаться ошибки других типов.
Если вызывающая goroutine заблокировала поток операционной системы с помощью runtime.LockOSThread и изменила любое наследуемое состояние потока на уровне ОС (например, пространства имен Linux или Plan 9), новый процесс унаследует состояние потока вызывающего.
Пример
package main
import (
"log"
"os/exec"
)
func main() {
cmd := exec.Command("sleep", "1")
log.Printf("Running command and waiting for it to finish...")
err := cmd.Run()
log.Printf("Command finished with error: %v", err)
}
func (*Cmd) Start
func (c *Cmd) Start() error
Start запускает указанную команду, но не ждет ее завершения.
Если Start возвращается успешно, поле c.Process будет установлено.
После успешного вызова Start необходимо вызвать метод Cmd.Wait, чтобы освободить связанные системные ресурсы.
Пример
package main
import (
"log"
"os/exec"
)
func main() {
cmd := exec.Command("sleep", "5")
err := cmd.Start()
if err != nil {
log.Fatal(err)
}
log.Printf("Waiting for command to finish...")
err = cmd.Wait()
log.Printf("Command finished with error: %v", err)
}
func (*Cmd) StderrPipe
func (c *Cmd) StderrPipe() (io.ReadCloser, error)
StderrPipe возвращает канал, который будет подключен к стандартной ошибке команды при ее запуске.
Cmd.Wait закроет канал после завершения команды, поэтому большинству вызывающих не нужно закрывать канал самостоятельно. Таким образом, неправильно вызывать Wait до завершения всех операций чтения из канала. По той же причине неправильно использовать Cmd.Run при использовании StderrPipe. Идиоматическое использование см. в примере StdoutPipe.
Пример
package main
import (
"fmt"
"io"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("sh", "-c", "echo stdout; echo 1>&2 stderr")
stderr, err := cmd.StderrPipe()
if err != nil {
log.Fatal(err)
}
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
slurp, _ := io.ReadAll(stderr)
fmt.Printf("%s\n", slurp)
if err := cmd.Wait(); err != nil {
log.Fatal(err)
}
}
func (*Cmd) StdinPipe
func (c *Cmd) StdinPipe() (io.WriteCloser, error)
StdinPipe возвращает канал, который будет подключен к стандартному входу команды при ее запуске. Канал будет автоматически закрыт после того, как Cmd.Wait увидит выход команды. Вызывающему достаточно вызвать Close, чтобы заставить канал закрыться раньше. Например, если запускаемая команда не завершится до закрытия стандартного ввода, вызывающий должен закрыть канал.
Пример
package main
import (
"fmt"
"io"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("cat")
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
}
go func() {
defer stdin.Close()
io.WriteString(stdin, "values written to stdin are passed to cmd's standard input")
}()
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", out)
}
func (*Cmd) StdoutPipe
func (c *Cmd) StdoutPipe() (io.ReadCloser, error)
StdoutPipe возвращает канал, который будет подключен к стандартному выводу команды при ее запуске.
Cmd.Wait закроет канал после завершения команды, поэтому большинству вызывающих не нужно закрывать канал самостоятельно. Таким образом, неправильно вызывать Wait до завершения всех операций чтения из канала. По той же причине неправильно вызывать Cmd.Run при использовании StdoutPipe. См. пример для идиоматического использования.
Пример
package main
import (
"encoding/json"
"fmt"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("echo", "-n", `{"Name": "Bob", "Age": 32}`)
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Fatal(err)
}
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
var person struct {
Name string
Age int
}
if err := json.NewDecoder(stdout).Decode(&person); err != nil {
log.Fatal(err)
}
if err := cmd.Wait(); err != nil {
log.Fatal(err)
}
fmt.Printf("%s is %d years old\n", person.Name, person.Age)
func (*Cmd) String
func (c *Cmd) String() string
String возвращает удобочитаемое описание c. Предназначено только для отладки. В частности, не подходит для использования в качестве ввода в оболочку. Вывод String может различаться в разных версиях Go.
func (*Cmd) Wait
func (c *Cmd) Wait() error
Wait ожидает завершения команды и завершения копирования в stdin или копирования из stdout или stderr.
Команда должна быть запущена с помощью Cmd.Start.
Возвращаемая ошибка равна nil, если команда запущена, не имеет проблем с копированием stdin, stdout и stderr и завершается с нулевым статусом выхода.
Если команда не запускается или не завершается успешно, ошибка имеет тип *ExitError. Другие типы ошибок могут возвращаться при проблемах с вводом-выводом.
Если c.Stdin, c.Stdout или c.Stderr не являются *os.File, Wait также ожидает завершения соответствующего цикла ввода-вывода в процесс или из процесса.
Wait освобождает все ресурсы, связанные с Cmd.
type Error
type Error struct {
// Name — имя файла, для которого произошла ошибка.
Name string
// Err — базовая ошибка.
Err error
}
Error возвращается LookPath, когда он не может классифицировать файл как исполняемый.
func (*Error) Error
func (e *Error) Error() string
func (*Error) Unwrap
func (e *Error) Unwrap() error
type ExitError
type ExitError struct {
*os.ProcessState
// Stderr содержит подмножество стандартного вывода ошибок из
// метода Cmd.Output, если стандартные ошибки не были
// собраны иным способом.
//
// Если вывод ошибок длинный, Stderr может содержать только префикс
// и суффикс вывода, а середина будет заменена
// текстом о количестве пропущенных байтов.
//
// Stderr предоставляется для отладки, для включения в сообщения об ошибках.
// Пользователи с другими потребностями должны перенаправить Cmd.Stderr по мере необходимости.
Stderr []byte
}
ExitError сообщает о неудачном завершении команды.
func (*ExitError) Error
func (e *ExitError) Error() string
11.4 - Пакет os/user языка программирования Go
Для большинства систем Unix этот пакет имеет две внутренние реализации преобразования идентификаторов пользователей и групп в имена, а также вывода списка дополнительных идентификаторов групп. Одна из них написана на чистом Go и анализирует файлы /etc/passwd и /etc/group. Другая основана на cgo и использует стандартные процедуры библиотеки C (libc), такие как getpwuid_r, getgrnam_r и getgrouplist.
Если cgo доступен, а необходимые процедуры реализованы в libc для конкретной платформы, используется код на основе cgo (с поддержкой libc). Это можно переопределить с помощью тега сборки osusergo, который принудительно использует реализацию на чистом Go.
type Group
type Group struct {
Gid string // идентификатор группы
Name string // название группы
}
Group представляет группу пользователей.
В системах POSIX Gid содержит десятичное число, представляющее идентификатор группы.
func LookupGroup
func LookupGroup(name string) (*Group, error)
LookupGroup ищет группу по имени. Если группа не найдена, возвращается ошибка типа UnknownGroupError.
func LookupGroupId
func LookupGroupId(gid string) (*Group, error)
LookupGroupId ищет группу по groupid. Если группа не найдена, возвращается ошибка типа UnknownGroupIdError.
type UnknownGroupError
type UnknownGroupError string
UnknownGroupError возвращается LookupGroup, когда группа не может быть найдена.
func (UnknownGroupError) Error
func (e UnknownGroupError) Error() string
type UnknownGroupIdError
type UnknownGroupIdError string
UnknownGroupIdError возвращается LookupGroupId, когда группа не может быть найдена.
func (UnknownGroupIdError) Error
func (e UnknownGroupIdError) Error() string
type UnknownUserError
type UnknownUserError string
UnknownUserError возвращается Lookup, когда пользователь не может быть найден.
func (UnknownUserError) Error
func (e UnknownUserError) Error() string
type UnknownUserIdError
type UnknownUserIdError int
UnknownUserIdError возвращается LookupId, когда пользователь не может быть найден.
func (UnknownUserIdError) Error
func (e UnknownUserIdError) Error() string
type User ¶
type User struct {
// Uid — это идентификатор пользователя.
// В системах POSIX это десятичное число, представляющее uid.
// В Windows это идентификатор безопасности (SID) в формате строки.
// В Plan 9 это содержимое /dev/user.
Uid string
// Gid — это идентификатор основной группы.
// В системах POSIX это десятичное число, представляющее gid.
// В Windows это SID в формате строки.
// В Plan 9 это содержимое /dev/user.
Gid string
// Username — это имя для входа в систему.
Username string
// Name — это настоящее или отображаемое имя пользователя.
// Оно может быть пустым.
// В системах POSIX это первая (или единственная) запись в поле GECOS
// списка GECOS.
// В Windows это отображаемое имя пользователя.
// В Plan 9 это содержимое /dev/user.
Строка имени
// HomeDir — это путь к домашнему каталогу пользователя (если он есть).
Строка HomeDir
}
User представляет учетную запись пользователя.
func Current
func Current() (*User, error)
Current возвращает текущего пользователя.
При первом вызове информация о текущем пользователе будет сохранена в кэше. Последующие вызовы будут возвращать значение из кэша и не будут отражать изменения текущего пользователя.
func Lookup
func Lookup(username string) (*User, error)
Lookup ищет пользователя по имени. Если пользователь не найден, возвращается ошибка типа UnknownUserError.
func LookupId
func LookupId(uid string) (*User, error)
LookupId ищет пользователя по идентификатору. Если пользователь не найден, возвращается ошибка типа UnknownUserIdError.
func (*User) GroupIds
func (u *User) GroupIds() ([]string, error)
GroupIds возвращает список идентификаторов групп, членом которых является пользователь.
11.5 - Пакет os/signal языка программирования Go
Сигналы в основном используются в Unix-подобных системах. Для использования этого пакета в Windows и Plan 9 см. ниже.
Типы сигналов
Сигналы SIGKILL и SIGSTOP не могут быть перехвачены программой и, следовательно, не могут быть затронуты этим пакетом.
Синхронные сигналы — это сигналы, вызываемые ошибками при выполнении программы: SIGBUS, SIGFPE и SIGSEGV. Они считаются синхронными только в том случае, если вызваны выполнением программы, а не отправлены с помощью os.Process.Kill, программы kill или аналогичного механизма. В общем случае, за исключением описанного ниже, программы Go преобразуют синхронный сигнал в панику во время выполнения.
Остальные сигналы являются асинхронными. Они не вызываются ошибками программы, а отправляются из ядра или из другой программы.
Из асинхронных сигналов сигнал SIGHUP отправляется, когда программа теряет свой управляющий терминал. Сигнал SIGINT отправляется, когда пользователь на управляющем терминале нажимает символ прерывания, который по умолчанию является ^C (Control-C). Сигнал SIGQUIT отправляется, когда пользователь на управляющем терминале нажимает символ выхода, который по умолчанию является ^\ (Control-Backslash). Как правило, вы можете просто завершить программу, нажав ^C, а также завершить ее с дампом стека, нажав ^.
Поведение сигналов по умолчанию в программах Go
По умолчанию синхронный сигнал преобразуется в панику во время выполнения. Сигнал SIGHUP, SIGINT или SIGTERM приводит к завершению программы. Сигнал SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGSTKFLT, SIGEMT или SIGSYS приводит к завершению программы с дампом стека. Сигнал SIGTSTP, SIGTTIN или SIGTTOU получает поведение по умолчанию системы (эти сигналы используются оболочкой для управления задачами). Сигнал SIGPROF обрабатывается непосредственно средой выполнения Go для реализации runtime.CPUProfile. Другие сигналы будут перехвачены, но никаких действий не будет предпринято.
Если программа Go запущена с игнорированием сигналов SIGHUP или SIGINT (обработчик сигналов установлен в SIG_IGN), они будут по-прежнему игнорироваться.
Если программа Go запущена с непустой маской сигналов, она, как правило, будет соблюдаться. Однако некоторые сигналы явно разблокированы: синхронные сигналы, SIGILL, SIGTRAP, SIGSTKFLT, SIGCHLD, SIGPROF, а в Linux — сигналы 32 (SIGCANCEL) и 33 (SIGSETXID) (SIGCANCEL и SIGSETXID используются внутренне glibc). Подпроцессы, запущенные с помощью os.Exec или os/exec, унаследуют измененную маску сигналов.
Изменение поведения сигналов в программах Go
Функции в этом пакете позволяют программе изменять способ обработки сигналов программами Go.
Notify отключает поведение по умолчанию для заданного набора асинхронных сигналов и вместо этого доставляет их по одному или нескольким зарегистрированным каналам. В частности, это относится к сигналам SIGHUP, SIGINT, SIGQUIT, SIGABRT и SIGTERM. Это также относится к сигналам управления задачами SIGTSTP, SIGTTIN и SIGTTOU, в этом случае поведение по умолчанию системы не происходит. Она также применяется к некоторым сигналам, которые в противном случае не вызывают никаких действий: SIGUSR1, SIGUSR2, SIGPIPE, SIGALRM, SIGCHLD, SIGCONT, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, SIGWINCH, SIGIO, SIGPWR, SIGINFO, SIGTHR, SIGWAITING, SIGLWP, SIGFREEZE, SIGTHAW, SIGLOST, SIGXRES, SIGJVM1, SIGJVM2 и любые сигналы реального времени, используемые в системе. Обратите внимание, что не все эти сигналы доступны во всех системах.
Если программа была запущена с игнорированием SIGHUP или SIGINT, и для любого из этих сигналов вызван Notify, для этого сигнала будет установлен обработчик сигналов, и он больше не будет игнорироваться. Если позже для этого сигнала вызван Reset или Ignore, или вызван Stop для всех каналов, переданных Notify для этого сигнала, сигнал снова будет игнорироваться. Reset восстановит поведение системы по умолчанию для сигнала, а Ignore заставит систему полностью игнорировать сигнал.
Если программа запущена с непустой маской сигналов, некоторые сигналы будут явно разблокированы, как описано выше. Если Notify вызывается для заблокированного сигнала, он будет разблокирован. Если позже для этого сигнала вызывается Reset или Stop для всех каналов, переданных Notify для этого сигнала, сигнал снова будет заблокирован.
SIGPIPE
Когда программа на Go записывает в разорванный канал, ядро генерирует сигнал SIGPIPE.
Если программа не вызвала Notify для получения сигналов SIGPIPE, то поведение зависит от номера файлового дескриптора. Запись в разорванный канал на файловых дескрипторах 1 или 2 (стандартный вывод или стандартная ошибка) приведет к завершению программы с сигналом SIGPIPE. Запись в разорванный канал на каком-либо другом файловом дескрипторе не вызовет никаких действий по сигналу SIGPIPE, и запись завершится с ошибкой EPIPE.
Если программа вызвала Notify для получения сигналов SIGPIPE, номер файлового дескриптора не имеет значения. Сигнал SIGPIPE будет доставлен в канал Notify, и запись завершится с ошибкой EPIPE.
Это означает, что по умолчанию программы командной строки будут вести себя как типичные программы командной строки Unix, в то время как другие программы не будут завершаться с SIGPIPE при записи в закрытое сетевое соединение.
Программы Go, использующие cgo или SWIG
В программе Go, которая включает код, не относящийся к Go, обычно код C/C++, доступ к которому осуществляется с помощью cgo или SWIG, сначала запускается код запуска Go. Он настраивает обработчики сигналов в соответствии с ожиданиями среды выполнения Go, прежде чем запускается код запуска, не относящийся к Go. Если код запуска, не относящийся к Go, желает установить свои собственные обработчики сигналов, он должен предпринять определенные шаги, чтобы Go продолжал работать нормально. В этом разделе описаны эти шаги и общие изменения, которые могут быть внесены в настройки обработчиков сигналов кодом, не относящимся к Go, в программах Go. В редких случаях код, не относящийся к Go, может запускаться перед кодом Go, и в этом случае также применяется следующий раздел.
Если код, не относящийся к Go, вызываемый программой Go, не изменяет обработчики сигналов или маски, то поведение будет таким же, как и для чистой программы Go.
Если код, не относящийся к Go, устанавливает какие-либо обработчики сигналов, он должен использовать флаг SA_ONSTACK с sigaction. Невыполнение этого требования может привести к сбою программы при получении сигнала. Программы Go обычно работают с ограниченным стеком, поэтому настраивают альтернативный стек сигналов.
Если код, не относящийся к Go, устанавливает обработчик сигналов для любого из синхронных сигналов (SIGBUS, SIGFPE, SIGSEGV), то он должен записывать существующий обработчик сигналов Go. Если эти сигналы возникают во время выполнения кода Go, он должен вызывать обработчик сигналов Go (возникновение сигнала во время выполнения кода Go можно определить, посмотрев на PC, передаваемый обработчику сигналов). В противном случае некоторые паники времени выполнения Go не будут происходить, как ожидается.
Если код, не относящийся к Go, устанавливает обработчик сигналов для любого из асинхронных сигналов, он может вызывать обработчик сигналов Go или нет, по своему усмотрению. Естественно, если он не вызывает обработчик сигналов Go, описанное выше поведение Go не будет происходить. Это может быть проблемой, в частности, с сигналом SIGPROF.
Код, не относящийся к Go, не должен изменять маску сигналов в любых потоках, созданных средой выполнения Go. Если код, не относящийся к Go, сам запускает новые потоки, эти потоки могут устанавливать маску сигналов по своему усмотрению.
Если код, не относящийся к Go, запускает новый поток, изменяет маску сигналов, а затем вызывает функцию Go в этом потоке, среда выполнения Go автоматически разблокирует определенные сигналы: синхронные сигналы, SIGILL, SIGTRAP, SIGSTKFLT, SIGCHLD, SIGPROF, SIGCANCEL и SIGSETXID. Когда функция Go возвращается, маска сигналов, не относящаяся к Go, будет восстановлена.
Если обработчик сигналов Go вызывается в потоке, не относящемся к Go и не выполняющем код Go, обработчик обычно пересылает сигнал в код, не относящийся к Go, следующим образом. Если сигнал является SIGPROF, обработчик Go ничего не делает. В противном случае обработчик Go удаляет себя, разблокирует сигнал и снова его генерирует, чтобы вызвать любой обработчик, не относящийся к Go, или системный обработчик по умолчанию. Если программа не завершается, обработчик Go переустанавливает себя и продолжает выполнение программы.
Если получен сигнал SIGPIPE, программа Go вызовет специальную обработку, описанную выше, если SIGPIPE получен в потоке Go. Если SIGPIPE получен в потоке, не относящемся к Go, сигнал будет перенаправлен в обработчик, не относящийся к Go, если таковой имеется; если его нет, обработчик по умолчанию системы приведет к завершению программы.
Программы, не написанные на Go, которые вызывают код Go
Когда код Go компилируется с такими опциями, как -buildmode=c-shared, он будет запускаться как часть существующей программы, не написанной на Go. Код, не написанный на Go, может уже иметь установленные обработчики сигналов, когда запускается код Go (это также может произойти в необычных случаях при использовании cgo или SWIG; в этом случае применимо обсуждение, приведенное здесь). Для -buildmode=c-archive среда выполнения Go инициализирует сигналы во время глобального конструктора. Для -buildmode=c-shared среда выполнения Go инициализирует сигналы при загрузке разделяемой библиотеки.
Если среда выполнения Go обнаруживает существующий обработчик сигналов для сигналов SIGCANCEL или SIGSETXID (которые используются только в Linux), она включает флаг SA_ONSTACK и в остальном сохраняет обработчик сигналов.
Для синхронных сигналов и SIGPIPE среда выполнения Go установит обработчик сигналов. Она сохранит любой существующий обработчик сигналов. Если синхронный сигнал поступает во время выполнения кода, не относящегося к Go, среда выполнения Go вызовет существующий обработчик сигналов вместо обработчика сигналов Go.
Код Go, скомпилированный с -buildmode=c-archive или -buildmode=c-shared, по умолчанию не будет устанавливать никаких других обработчиков сигналов. Если существующий обработчик сигналов, среда выполнения Go включит флаг SA_ONSTACK и сохранит обработчик сигналов. Если Notify вызывается для асинхронного сигнала, для этого сигнала будет установлен обработчик сигналов Go. Если позже для этого сигнала вызывается Reset, исходная обработка этого сигнала будет переустановлена, восстанавливая обработчик сигналов, не относящийся к Go, если таковой имеется.
Код Go, скомпилированный без -buildmode=c-archive или -buildmode=c-shared, установит обработчик сигналов для асинхронных сигналов, перечисленных выше, и сохранит любой существующий обработчик сигналов. Если сигнал доставляется в поток, не относящийся к Go, он будет действовать, как описано выше, за исключением того, что если существует обработчик сигналов, не относящийся к Go, этот обработчик будет установлен перед генерацией сигнала. Windows ¶
В Windows ^C (Control-C) или ^BREAK (Control-Break) обычно приводят к завершению программы. Если Notify вызывается для os.Interrupt, ^C или ^BREAK приводят к отправке os.Interrupt по каналу, и программа не завершается. Если вызывается Reset или Stop на всех каналах, переданных Notify, то поведение по умолчанию будет восстановлено.
Кроме того, если вызывается Notify, и Windows отправляет CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT или CTRL_SHUTDOWN_EVENT процессу, Notify вернет syscall.SIGTERM. В отличие от Control-C и Control-Break, Notify не изменяет поведение процесса при получении CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT или CTRL_SHUTDOWN_EVENT — процесс все равно будет завершен, если он не завершится самостоятельно. Но получение syscall.SIGTERM даст процессу возможность очиститься перед завершением.
Plan 9
В Plan 9 сигналы имеют тип syscall.Note, который представляет собой строку. Вызов Notify с syscall.Note приведет к отправке этого значения по каналу, когда эта строка будет отправлена в качестве заметки.
Функции
func Ignore
func Ignore(sig ...os.Signal)
Ignore заставляет игнорировать указанные сигналы. Если они будут получены программой, ничего не произойдет. Ignore отменяет действие всех предыдущих вызовов Notify для указанных сигналов. Если сигналы не указаны, все входящие сигналы будут игнорироваться.
func Ignored
func Ignored(sig os.Signal) bool
Ignored сообщает, игнорируется ли sig в данный момент.
func Notify
func Notify(c chan<- os.Signal, sig ...os.Signal)
Notify заставляет пакет signal ретранслировать входящие сигналы в c. Если сигналы не предоставлены, все входящие сигналы будут ретранслированы в c. В противном случае будут ретранслированы только предоставленные сигналы.
Пакет signal не будет блокировать отправку в c: вызывающий должен убедиться, что c имеет достаточное буферное пространство, чтобы справиться с ожидаемой скоростью сигналов. Для канала, используемого для уведомления только об одном значении сигнала, достаточно буфера размером 1.
Разрешается вызывать Notify несколько раз с одним и тем же каналом: каждый вызов расширяет набор сигналов, отправляемых на этот канал. Единственный способ удалить сигналы из набора — вызвать Stop.
Разрешается вызывать Notify несколько раз с разными каналами и одними и теми же сигналами: каждый канал получает копии входящих сигналов независимо.
Пример
package main
import (
"fmt"
"os"
"os/signal"
)
func main() {
// Set up channel on which to send signal notifications.
// We must use a buffered channel or risk missing the signal
// if we're not ready to receive when the signal is sent.
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
// Block until a signal is received.
s := <-c
fmt.Println("Got signal:", s)
}
Пример AllSignals
package main
import (
"fmt"
"os"
"os/signal"
)
func main() {
// Set up channel on which to send signal notifications.
// We must use a buffered channel or risk missing the signal
// if we're not ready to receive when the signal is sent.
c := make(chan os.Signal, 1)
// Passing no signals to Notify means that
// all signals will be sent to the channel.
signal.Notify(c)
// Block until any signal is received.
s := <-c
fmt.Println("Got signal:", s)
}
func NotifyContext
func NotifyContext(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc)
NotifyContext возвращает копию родительского контекста, который помечается как выполненный (его канал Done закрывается), когда поступает один из перечисленных сигналов, когда вызывается возвращаемая функция stop или когда канал Done родительского контекста закрывается, в зависимости от того, что произойдет раньше.
Функция stop отменяет регистрацию поведения сигнала, что, как и signal.Reset, может восстановить поведение по умолчанию для данного сигнала. Например, поведением по умолчанию для программы Go, получающей os.Interrupt, является выход. Вызов NotifyContext(parent, os.Interrupt) изменит поведение на отмену возвращенного контекста. Будущие прерывания не будут вызывать поведение по умолчанию (выход) до тех пор, пока не будет вызвана возвращенная функция stop.
Функция stop освобождает связанные с ней ресурсы, поэтому код должен вызывать stop, как только операции, выполняемые в этом контексте, завершатся и сигналы больше не нужно будет перенаправлять в контекст.
Пример
//go:build unix
package main
import (
"context"
"fmt"
"log"
"os"
"os/signal"
)
var neverReady = make(chan struct{}) // never closed
// This example passes a context with a signal to tell a blocking function that
// it should abandon its work after a signal is received.
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
p, err := os.FindProcess(os.Getpid())
if err != nil {
log.Fatal(err)
}
// On a Unix-like system, pressing Ctrl+C on a keyboard sends a
// SIGINT signal to the process of the program in execution.
//
// This example simulates that by sending a SIGINT signal to itself.
if err := p.Signal(os.Interrupt); err != nil {
log.Fatal(err)
}
select {
case <-neverReady:
fmt.Println("ready")
case <-ctx.Done():
fmt.Println(ctx.Err()) // prints "context canceled"
stop() // stop receiving signal notifications as soon as possible.
}
}
Output:
context canceledfunc Reset
func Reset(sig ...os.Signal)
Reset отменяет эффект всех предыдущих вызовов Notify для предоставленных сигналов. Если сигналы не указаны, все обработчики сигналов будут сброшены.
func Stop
func Stop(c chan<- os.Signal)
Stop заставляет пакет signal прекратить ретрансляцию входящих сигналов в c. Он отменяет эффект всех предыдущих вызовов Notify с использованием c. Когда Stop возвращается, гарантируется, что c больше не будет получать сигналы.
12 - Описание пакета strconv
Числовые преобразования
Наиболее распространенными числовыми преобразованиями являются Atoi (строка в int) и Itoa (int в String).
i, err := strconv.Atoi(«-42»)
s := strconv.Itoa(-42)
Они предполагают десятичную систему счисления и тип Go int.
ParseBool, ParseFloat, ParseInt и ParseUint преобразуют строки в значения:
b, err := strconv.ParseBool(«true»)
f, err := strconv.ParseFloat(«3.1415», 64)
i, err := strconv.ParseInt(«-42», 10, 64)
u, err := strconv.ParseUint(«42», 10, 64)
Функции преобразования возвращают самый широкий тип (float64, int64 и uint64), но если аргумент size указывает более узкую ширину, результат может быть преобразован в этот более узкий тип без потери данных:
s := «2147483647» // самый большой int32
i64, err := strconv.ParseInt(s, 10, 32)
...
i := int32(i64)
FormatBool, FormatFloat, FormatInt и FormatUint преобразуют значения в строки:
s := strconv.FormatBool(true)
s := strconv.FormatFloat(3.1415, „E“, -1, 64)
s := strconv.FormatInt(-42, 16)
s := strconv.FormatUint(42, 16)
AppendBool, AppendFloat, AppendInt и AppendUint аналогичны, но добавляют отформатированное значение в целевой срез.
Преобразование строк
Quote и QuoteToASCII преобразуют строки в строчные литералы Go в кавычках. Последний гарантирует, что результатом будет строка ASCII, экранируя любой не-ASCII Unicode с помощью \u:
q := strconv.Quote(«Hello, 世界»)
q := strconv.QuoteToASCII(«Hello, 世界»)
QuoteRune и QuoteRuneToASCII похожи, но принимают руны и возвращают зацикленные литералы Go.
Unquote и UnquoteChar разъединяют литералы строк и рун Go.
Константы
const IntSize = intSize
IntSize - это размер в битах значения int или uint.
Переменные
var ErrRange = errors.New("значение вне диапазона")
ErrRange указывает, что значение выходит за пределы диапазона для целевого типа.
var ErrSyntax = errors.New("invalid syntax")
ErrSyntax указывает, что значение не имеет правильного синтаксиса для целевого типа.
12.1 - Описание функций пакета strconv
func AppendBool
func AppendBool(dst []byte, b bool) []byte
AppendBool добавляет «true» или «false» в зависимости от значения b к dst и возвращает расширенный буфер.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
b := []byte("bool:")
b = strconv.AppendBool(b, true)
fmt.Println(string(b))
}
func AppendFloat
func AppendFloat(dst []byte, f float64, fmt byte, prec, bitSize int) []byte
AppendFloat добавляет строковую форму числа с плавающей запятой f, сгенерированную FormatFloat, к dst и возвращает расширенный буфер.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
b32 := []byte("float32:")
b32 = strconv.AppendFloat(b32, 3.1415926535, 'E', -1, 32)
fmt.Println(string(b32))
b64 := []byte("float64:")
b64 = strconv.AppendFloat(b64, 3.1415926535, 'E', -1, 64)
fmt.Println(string(b64))
}
func AppendInt
func AppendInt(dst []byte, i int64, base int) []byte
AppendInt добавляет строковую форму целого числа i, сгенерированную FormatInt, к dst и возвращает расширенный буфер.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
b10 := []byte("int (base 10):")
b10 = strconv.AppendInt(b10, -42, 10)
fmt.Println(string(b10))
b16 := []byte("int (base 16):")
b16 = strconv.AppendInt(b16, -42, 16)
fmt.Println(string(b16))
}
func AppendQuote
func AppendQuote(dst []byte, s string) []byte
AppendQuote добавляет к dst строковый литерал Go в двойных кавычках, представляющий s, сгенерированный Quote, и возвращает расширенный буфер.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
b := []byte("quote:")
b = strconv.AppendQuote(b, `"Fran & Freddie's Diner"`)
fmt.Println(string(b))
}
Output:
quote:"\"Fran & Freddie's Diner\""
func AppendQuoteRune
func AppendQuoteRune(dst []byte, r rune) []byte
AppendQuoteRune добавляет к dst однострочный литерал Go, представляющий руну, сгенерированный QuoteRune, и возвращает расширенный буфер.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
b := []byte("rune:")
b = strconv.AppendQuoteRune(b, '☺')
fmt.Println(string(b))
}
Output:
rune:'☺'
func AppendQuoteRuneToASCII
func AppendQuoteRuneToASCII(dst []byte, r rune) []byte
AppendQuoteRuneToASCII добавляет в dst однострочный литерал символа Go, представляющий руну, сгенерированный QuoteRuneToASCII, и возвращает расширенный буфер.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
b := []byte("rune (ascii):")
b = strconv.AppendQuoteRuneToASCII(b, '☺')
fmt.Println(string(b))
}
Output:
rune (ascii):'\u263a'
func AppendQuoteRuneToGraphic
func AppendQuoteRuneToGraphic(dst []byte, r rune) []byte
AppendQuoteRuneToGraphic добавляет к dst однострочный литерал символа Go, представляющий руну, сгенерированный QuoteRuneToGraphic, и возвращает расширенный буфер.
func AppendQuoteToASCII
func AppendQuoteToASCII(dst []byte, s string) []byte
AppendQuoteToASCII добавляет к dst строковый литерал Go в двойных кавычках, представляющий s, сгенерированный QuoteToASCII, и возвращает расширенный буфер.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
b := []byte("quote (ascii):")
b = strconv.AppendQuoteToASCII(b, `"Fran & Freddie's Diner"`)
fmt.Println(string(b))
}
Output:
quote (ascii):"\"Fran & Freddie's Diner\""
func AppendQuoteToGraphic
func AppendQuoteToGraphic(dst []byte, s string) []byte
AppendQuoteToGraphic добавляет к dst строковый литерал Go в двойных кавычках, представляющий s, сгенерированный QuoteToGraphic, и возвращает расширенный буфер.
func AppendUint
func AppendUint(dst []byte, i uint64, base int) []byte
AppendUint добавляет строковую форму целого числа без знака i, сгенерированную FormatUint, к dst и возвращает расширенный буфер.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
b10 := []byte("uint (base 10):")
b10 = strconv.AppendUint(b10, 42, 10)
fmt.Println(string(b10))
b16 := []byte("uint (base 16):")
b16 = strconv.AppendUint(b16, 42, 16)
fmt.Println(string(b16))
}
Output:
uint (base 10):42
uint (base 16):2a
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
v := "10"
if s, err := strconv.Atoi(v); err == nil {
fmt.Printf("%T, %v", s, s)
}
}
Output:
int, 10
func CanBackquote
func CanBackquote(s string) bool
CanBackquote сообщает, может ли строка s быть представлена без изменений в виде однострочной строки с обратными кавычками, не содержащей контрольных символов, кроме табуляции.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
fmt.Println(strconv.CanBackquote("Fran & Freddie's Diner ☺"))
fmt.Println(strconv.CanBackquote("`can't backquote this`"))
}
Output:
true
false
func FormatBool
func FormatBool(b bool) string
FormatBool возвращает «true» или «false» в зависимости от значения b.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
v := true
s := strconv.FormatBool(v)
fmt.Printf("%T, %v\n", s, s)
}
Output:
string, true
func FormatComplex
func FormatComplex(c complex128, fmt byte, prec, bitSize int) string
FormatComplex преобразует комплексное число c в строку вида (a+bi), где a и b — действительная и мнимая части, отформатированные в соответствии с форматом fmt и точностью prec.
Формат fmt и точность prec имеют то же значение, что и в FormatFloat. Он округляет результат, предполагая, что исходное значение было получено из комплексного значения bitSize бит, которое должно быть 64 для complex64 и 128 для complex128.
func FormatFloat
func FormatFloat(f float64, fmt byte, prec, bitSize int) string
FormatFloat преобразует число с плавающей запятой f в строку в соответствии с форматом fmt и точностью prec. Оно округляет результат, предполагая, что исходное значение было получено из значения с плавающей запятой bitSize бит (32 для float32, 64 для float64).
Формат fmt может быть одним из следующих
- “b” (-ddddp±ddd, двоичный экспонента),
- “e” (-d.dddde±dd, десятичный экспонент),
- “E” (-d.ddddE±dd, десятичный экспонент),
- “f” (-ddd.dddd, без экспонента),
- “g” („e“ для больших экспонентов, „f“ в остальных случаях),
- “G” („E“ для больших экспонентов, „f“ в остальных случаях),
- “x” (-0xd.ddddp±ddd, шестнадцатеричная дробь и двоичный показатель), или
- “X” (-0Xd.ddddP±ddd, шестнадцатеричная дробь и двоичный показатель).
Точность prec контролирует количество цифр (исключая экспоненту), выводимых форматами „e“, „E“, „f“, „g“, „G“, „x“ и „X“. Для „e“, „E“, „f“, „x“ и „X“ это количество цифр после десятичной запятой. Для „g“ и „G“ это максимальное количество значимых цифр (конечные нули удаляются). Специальная точность -1 использует минимальное количество цифр, необходимое для того, чтобы ParseFloat вернул точное значение f. Экспонента записывается в виде десятичного целого числа; для всех форматов, кроме „b“, она будет состоять как минимум из двух цифр.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
v := 3.1415926535
s32 := strconv.FormatFloat(v, 'E', -1, 32)
fmt.Printf("%T, %v\n", s32, s32)
s64 := strconv.FormatFloat(v, 'E', -1, 64)
fmt.Printf("%T, %v\n", s64, s64)
// fmt.Println uses these arguments to print floats
fmt64 := strconv.FormatFloat(v, 'g', -1, 64)
fmt.Printf("%T, %v\n", fmt64, fmt64)
}
Output:
string, 3.1415927E+00
string, 3.1415926535E+00
string, 3.1415926535
func FormatInt
func FormatInt(i int64, base int) string
FormatInt возвращает строковое представление i в заданной базе, для 2 <= base <= 36. Результат использует строчные буквы от «a» до «z» для значений цифр >= 10.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
v := int64(-42)
s10 := strconv.FormatInt(v, 10)
fmt.Printf("%T, %v\n", s10, s10)
s16 := strconv.FormatInt(v, 16)
fmt.Printf("%T, %v\n", s16, s16)
}
Output:
string, -42
string, -2a
func FormatUint
func FormatUint(i uint64, base int) string
FormatUint возвращает строковое представление i в заданной базе, для 2 <= base <= 36. В результате для значений цифр >= 10 используются строчные буквы от „a“ до „z“.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
v := uint64(42)
s10 := strconv.FormatUint(v, 10)
fmt.Printf("%T, %v\n", s10, s10)
s16 := strconv.FormatUint(v, 16)
fmt.Printf("%T, %v\n", s16, s16)
}
Output:
string, 42
string, 2a
func IsGraphic
func IsGraphic(r rune) bool
IsGraphic сообщает, определена ли руна как графический символ в Unicode. К таким символам относятся буквы, знаки, цифры, знаки препинания, символы и пробелы из категорий L, M, N, P, S и Zs.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
shamrock := strconv.IsGraphic('☘')
fmt.Println(shamrock)
a := strconv.IsGraphic('a')
fmt.Println(a)
bel := strconv.IsGraphic('\007')
fmt.Println(bel)
}
Output:
true
true
false
func IsPrint
func IsPrint(r rune) bool
IsPrint сообщает, определена ли руна как печатная в Go, с тем же определением, что и unicode.IsPrint: буквы, цифры, знаки препинания, символы и пробел ASCII.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
c := strconv.IsPrint('\u263a')
fmt.Println(c)
bel := strconv.IsPrint('\007')
fmt.Println(bel)
}
Output:
true
false
func Itoa
func Itoa(i int) string
Itoa эквивалентна FormatInt(int64(i), 10).
package main
import (
"fmt"
"strconv"
)
func main() {
i := 10
s := strconv.Itoa(i)
fmt.Printf("%T, %v\n", s, s)
}
Output:
string, 10
func ParseBool
func ParseBool(str string) (bool, error)
ParseBool возвращает булево значение, представленное строкой. Принимает 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False. Любое другое значение возвращает ошибку.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
v := "true"
if s, err := strconv.ParseBool(v); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
}
Output:
bool, true
func ParseComplex
func ParseComplex(s string, bitSize int) (complex128, error)
ParseComplex преобразует строку s в комплексное число с точностью, указанной в bitSize: 64 для complex64 или 128 для complex128. Когда bitSize=64, результат по-прежнему имеет тип complex128, но его можно преобразовать в complex64 без изменения его значения.
Число, представленное s, должно иметь вид N, Ni или N±Ni, где N обозначает число с плавающей запятой, распознаваемое ParseFloat, а i — мнимую составляющую. Если второе N не имеет знака, между двумя составляющими требуется знак +, как указано ±. Если второе N равно NaN, допускается только знак +. Форма может быть заключена в скобки и не может содержать пробелов. Результирующее комплексное число состоит из двух компонентов, преобразованных ParseFloat.
Ошибки, возвращаемые ParseComplex, имеют конкретный тип *NumError и включают err.Num = s.
Если s не является синтаксически правильным, ParseComplex возвращает err.Err = ErrSyntax.
Если s является синтаксически правильным, но любой из компонентов находится на расстоянии более 1/2 ULP от наибольшего числа с плавающей запятой заданного размера компонента, ParseComplex возвращает err.Err = ErrRange и c = ±Inf для соответствующего компонента.
func ParseFloat
func ParseFloat(s string, bitSize int) (float64, error)
ParseFloat преобразует строку s в число с плавающей запятой с точностью, указанной в bitSize: 32 для float32 или 64 для float64. Когда bitSize=32, результат по-прежнему имеет тип float64, но его можно преобразовать в float32 без изменения его значения.
ParseFloat принимает десятичные и шестнадцатеричные числа с плавающей запятой, как определено синтаксисом Go для литералов с плавающей запятой. Если s имеет правильную форму и близка к действительному числу с плавающей запятой, ParseFloat возвращает ближайшее число с плавающей запятой, округленное с использованием беспристрастного округления IEEE754. (При разборе шестнадцатеричного числа с плавающей запятой округление происходит только в том случае, если в шестнадцатеричном представлении больше битов, чем может поместиться в мантиссе).
Ошибки, которые возвращает ParseFloat, имеют конкретный тип *NumError и включают err.Num = s.
Если s не является синтаксически правильно сформированным, ParseFloat возвращает err.Err = ErrSyntax.
Если s является синтаксически правильным, но находится на расстоянии более 1/2 ULP от наибольшего числа с плавающей запятой заданного размера, ParseFloat возвращает f = ±Inf, err.Err = ErrRange.
ParseFloat распознает строку «NaN» и строки «Inf» и «Infinity» (возможно со знаком) как соответствующие специальные значения с плавающей запятой. При сопоставлении он игнорирует регистр.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
v := "3.1415926535"
if s, err := strconv.ParseFloat(v, 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseFloat(v, 64); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseFloat("NaN", 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
// ParseFloat is case insensitive
if s, err := strconv.ParseFloat("nan", 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseFloat("inf", 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseFloat("+Inf", 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseFloat("-Inf", 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseFloat("-0", 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseFloat("+0", 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
}
Output:
float64, 3.1415927410125732
float64, 3.1415926535
float64, NaN
float64, NaN
float64, +Inf
float64, +Inf
float64, -Inf
float64, -0
float64, 0
func ParseInt
func ParseInt(s string, base int, bitSize int) (i int64, err error)
ParseInt интерпретирует строку s в заданной базе (от 0 до 36) и размере бита (от 0 до 64) и возвращает соответствующее значение i.
Строка может начинаться с ведущего знака: «+» или «-».
Если аргумент base равен 0, истинная база подразумевается префиксом строки, следующим за знаком (если он есть): 2 для «0b», 8 для «0» или «0o», 16 для «0x» и 10 в остальных случаях. Кроме того, только для аргумента base 0 допускаются символы подчеркивания, как определено синтаксисом Go для целочисленных литералов.
Аргумент bitSize указывает тип целого числа, в который должен помещаться результат. Размеры битов 0, 8, 16, 32 и 64 соответствуют int, int8, int16, int32 и int64. Если bitSize меньше 0 или больше 64, возвращается ошибка.
Ошибки, которые возвращает ParseInt, имеют конкретный тип *NumError и включают err.Num = s. Если s пустое или содержит недопустимые цифры, err.Err = ErrSyntax, и возвращаемое значение равно 0; если значение, соответствующее s, не может быть представлено целым числом с знаком заданного размера, err.Err = ErrRange, и возвращаемое значение является целым числом максимальной величины с соответствующим bitSize и знаком.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
v32 := "-354634382"
if s, err := strconv.ParseInt(v32, 10, 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseInt(v32, 16, 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
v64 := "-3546343826724305832"
if s, err := strconv.ParseInt(v64, 10, 64); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseInt(v64, 16, 64); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
}
Output:
int64, -354634382
int64, -3546343826724305832
func ParseUint
func ParseUint(s string, base int, bitSize int) (uint64, error)
ParseUint похож на ParseInt, но для чисел без знака.
Префикс знака не допускается.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
v := "42"
if s, err := strconv.ParseUint(v, 10, 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseUint(v, 10, 64); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
}
Output:
uint64, 42
uint64, 42
func Quote
func Quote(s string) string
Quote возвращает строковый литерал Go в двойных кавычках, представляющий s. Возвращаемая строка использует экранирующие последовательности Go (\t, \n, \xFF, \u0100) для управляющих символов и непечатаемых символов, как определено в IsPrint.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
// This string literal contains a tab character.
s := strconv.Quote(`"Fran & Freddie's Diner ☺"`)
fmt.Println(s)
}
Output:
"\"Fran & Freddie's Diner\t☺\""
func QuoteRune
func QuoteRune(r rune) string
QuoteRune возвращает строковый литерал Go в одинарных кавычках, представляющий руну. Возвращаемая строка использует экранирующие последовательности Go (\t, \n, \xFF, \u0100) для управляющих символов и непечатаемых символов, как определено в IsPrint. Если r не является действительным кодом Unicode, он интерпретируется как символ замены Unicode U+FFFD.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
s := strconv.QuoteRune('☺')
fmt.Println(s)
}
Output:
'☺'
func QuoteRuneToASCII
func QuoteRuneToASCII(r rune) string
QuoteRuneToASCII возвращает однострочный литерал символа Go, представляющий руну. Возвращаемая строка использует экранирующие последовательности Go (\t, \n, \xFF, \u0100) для не-ASCII символов и непечатаемых символов, как определено в IsPrint. Если r не является действительным кодом Unicode, он интерпретируется как заменяющий символ Unicode U+FFFD.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
s := strconv.QuoteRuneToASCII('☺')
fmt.Println(s)
}
Output:
'\u263a'
func QuoteRuneToGraphic
func QuoteRuneToGraphic(r rune) string
QuoteRuneToGraphic возвращает однострочный литерал Go, представляющий руну. Если руна не является графическим символом Unicode, как определено IsGraphic, возвращаемая строка будет использовать экранирующие последовательности Go (\t, \n, \xFF, \u0100). Если r не является допустимым кодовым пунктом Unicode, он интерпретируется как символ замены Unicode U+FFFD.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
s := strconv.QuoteRuneToGraphic('☺')
fmt.Println(s)
s = strconv.QuoteRuneToGraphic('\u263a')
fmt.Println(s)
s = strconv.QuoteRuneToGraphic('\u000a')
fmt.Println(s)
s = strconv.QuoteRuneToGraphic(' ') // tab character
fmt.Println(s)
}
Output:
'☺'
'☺'
'\n'
'\t'
func QuoteToASCII
func QuoteToASCII(s string) string
QuoteToASCII возвращает строковый литерал Go в двойных кавычках, представляющий s. Возвращаемая строка использует экранирующие последовательности Go (\t, \n, \xFF, \u0100) для не-ASCII символов и непечатаемых символов, как определено IsPrint.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
// This string literal contains a tab character.
s := strconv.QuoteToASCII(`"Fran & Freddie's Diner ☺"`)
fmt.Println(s)
}
Output:
"\"Fran & Freddie's Diner\t\u263a\""
func QuoteToGraphic
func QuoteToGraphic(s string) string
QuoteToGraphic возвращает строковый литерал Go в двойных кавычках, представляющий s. Возвращаемая строка оставляет графические символы Unicode, как определено IsGraphic, без изменений и использует экранирующие последовательности Go (\t, \n, \xFF, \u0100) для неграфических символов.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
s := strconv.QuoteToGraphic("☺")
fmt.Println(s)
// This string literal contains a tab character.
s = strconv.QuoteToGraphic("This is a \u263a \u000a")
fmt.Println(s)
s = strconv.QuoteToGraphic(`" This is a ☺ \n "`)
fmt.Println(s)
}
Output:
"☺"
"This is a ☺\t\n"
"\" This is a ☺ \\n \""
func QuotedPrefix
func QuotedPrefix(s string) (string, error)
QuotedPrefix возвращает строку в кавычках (как понимает Unquote) в префиксе s. Если s не начинается с действительной строки в кавычках, QuotedPrefix возвращает ошибку.
package main
import (
"fmt"
"strconv"
)
func main() {
s, err := strconv.QuotedPrefix("not a quoted string")
fmt.Printf("%q, %v\n", s, err)
s, err = strconv.QuotedPrefix("\"double-quoted string\" with trailing text")
fmt.Printf("%q, %v\n", s, err)
s, err = strconv.QuotedPrefix("`or backquoted` with more trailing text")
fmt.Printf("%q, %v\n", s, err)
s, err = strconv.QuotedPrefix("'\u263a' is also okay")
fmt.Printf("%q, %v\n", s, err)
}
Output:
"", invalid syntax
"\"double-quoted string\"", <nil>
"`or backquoted`", <nil>
"'☺'", <nil>
func Unquote
func Unquote(s string) (string, error)
Unquote интерпретирует s как строковый литерал Go в одинарных, двойных или обратных кавычках, возвращая значение строки, которое s заключает в кавычки. (Если s заключено в одинарные кавычки, это будет символьный литерал Go; Unquote возвращает соответствующую односимвольную строку. Для пустого символьного литерала Unquote возвращает пустую строку.)
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
s, err := strconv.Unquote("You can't unquote a string without quotes")
fmt.Printf("%q, %v\n", s, err)
s, err = strconv.Unquote("\"The string must be either double-quoted\"")
fmt.Printf("%q, %v\n", s, err)
s, err = strconv.Unquote("`or backquoted.`")
fmt.Printf("%q, %v\n", s, err)
s, err = strconv.Unquote("'\u263a'") // single character only allowed in single quotes
fmt.Printf("%q, %v\n", s, err)
s, err = strconv.Unquote("'\u2639\u2639'")
fmt.Printf("%q, %v\n", s, err)
}
Output:
"", invalid syntax
"The string must be either double-quoted", <nil>
"or backquoted.", <nil>
"☺", <nil>
"", invalid syntax
func UnquoteChar
func UnquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error)
UnquoteChar декодирует первый символ или байт в экранированной строке или символьном литерале, представленном строкой s. Он возвращает четыре значения:
value, декодированный код Unicode или значение байта; multibyte, булево значение, указывающее, требует ли декодированный символ многобайтового представления UTF-8; tail, остаток строки после символа; и ошибка, которая будет nil, если символ синтаксически валиден. Второй аргумент, quote, указывает тип анализируемого литерала и, следовательно, какой экранированный символ кавычки разрешен. Если установлен в одиночную кавычку, он разрешает последовательность \„ и запрещает неэкранированный “. Если установлен в двойную кавычку, он разрешает \« и запрещает неэкранированный ». Если установлен в ноль, он не разрешает ни один из экранированных символов и позволяет обоим символам кавычки появляться неэкранированными.
Пример
package main
import (
"fmt"
"log"
"strconv"
)
func main() {
v, mb, t, err := strconv.UnquoteChar(`\"Fran & Freddie's Diner\"`, '"')
if err != nil {
log.Fatal(err)
}
fmt.Println("value:", string(v))
fmt.Println("multibyte:", mb)
fmt.Println("tail:", t)
}
Output:
value: "
multibyte: false
tail: Fran & Freddie's Diner\"
12.2 - Тип NumError пакета strconv
type NumError
type NumError struct {
Func string // сбойная функция (ParseBool, ParseInt, ParseUint, ParseFloat, ParseComplex)
Num string // входные данные
Err error // причина сбоя преобразования (например, ErrRange, ErrSyntax и т. д.)
}
Ошибка NumError фиксирует неудачное преобразование.
Пример
package main
import (
"fmt"
"strconv"
)
func main() {
str := "Not a number"
if _, err := strconv.ParseFloat(str, 64); err != nil {
e := err.(*strconv.NumError)
fmt.Println("Func:", e.Func)
fmt.Println("Num:", e.Num)
fmt.Println("Err:", e.Err)
fmt.Println(err)
}
}
Output:
Func: ParseFloat
Num: Not a number
Err: invalid syntax
strconv.ParseFloat: parsing "Not a number": invalid syntax
func (*NumError) Error
func (e *NumError) Error() string
func (*NumError) Unwrap
func (e *NumError) Unwrap() error
13 - Описание пакета error языка программирования Go
Функция New создает ошибки, содержащие только текстовое сообщение.
Ошибка e оборачивает другую ошибку, если тип e имеет один из методов:
Unwrap() error
Unwrap() []error
Если e.Unwrap() возвращает не nil ошибку w или срез, содержащий w, то говорится, что e оборачивает w. Возвращение nil ошибки из e.Unwrap() указывает на то, что e не оборачивает никакую ошибку. Недопустимо, чтобы метод Unwrap() возвращал срез, содержащий nil значение ошибки.
Легкий способ создать обернутые ошибки — вызвать fmt.Errorf и применить шаблон %w к аргументу ошибки:
wrapsErr := fmt.Errorf("... %w ...", ..., err, ...)
Последовательное разворачивание ошибки создает дерево. Функции Is и As исследуют дерево ошибки, проверяя сначала саму ошибку, а затем дерево каждого из ее потомков по очереди (префиксный, глубинный обход).
См. https://go.dev/blog/go1.13-errors для более глубокого обсуждения философии оборачивания и когда следует оборачивать.
Функция Is исследует дерево своего первого аргумента в поисках ошибки, совпадающей со вторым. Она сообщает, найдено ли совпадение. Ее следует использовать вместо простых проверок на равенство:
if errors.Is(err, fs.ErrExist)
лучше, чем
if err == fs.ErrExist
потому что первая будет успешной, если err оборачивает io/fs.ErrExist.
Функция As исследует дерево своего первого аргумента в поисках ошибки, которую можно присвоить второму аргументу, который должен быть указателем. Если она успешна, она выполняет присваивание и возвращает true. В противном случае она возвращает false. Форма
var perr *fs.PathError
if errors.As(err, &perr) {
fmt.Println(perr.Path)
}
лучше, чем
if perr, ok := err.(*fs.PathError); ok {
fmt.Println(perr.Path)
}
потому что первая будет успешной, если err оборачивает *io/fs.PathError.
Пример
package main
import (
"fmt"
"time"
)
// MyError is an error implementation that includes a time and message.
type MyError struct {
When time.Time
What string
}
func (e MyError) Error() string {
return fmt.Sprintf("%v: %v", e.When, e.What)
}
func oops() error {
return MyError{
time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC),
"the file system has gone away",
}
}
func main() {
if err := oops(); err != nil {
fmt.Println(err)
}
}
Output:
1989-03-15 22:30:00 +0000 UTC: the file system has gone awayПеременные
var ErrUnsupported = New("unsupported operation")
ErrUnsupported указывает на то, что запрашиваемая операция не может быть выполнена, потому что она не поддерживается. Например, вызов os.Link при использовании файловой системы, которая не поддерживает жесткие ссылки.
Функции и методы не должны возвращать эту ошибку, а вместо этого должны возвращать ошибку, включающую соответствующий контекст, который удовлетворяет
errors.Is(err, errors.ErrUnsupported)
либо путем прямого оборачивания ErrUnsupported, либо путем реализации метода Is.
Функции и методы должны документировать случаи, в которых будет возвращена ошибка, оборачивающая эту.
Функции
func As
Объяснение As
📌 Пример 1: Простая кастомная ошибка
Допустим, у нас есть своя ошибка с дополнительными полями:
type NetworkError struct {
Code int
Message string
}
// Реализуем метод Error(), чтобы NetworkError соответствовала интерфейсу error
func (e *NetworkError) Error() string {
return fmt.Sprintf("Network error %d: %s", e.Code, e.Message)
}
Теперь создадим ошибку и проверим её тип с помощью As:
func main() {
err := &NetworkError{Code: 404, Message: "Not Found"} // создаём ошибку
var netErr *NetworkError
if errors.As(err, &netErr) { // проверяем тип и извлекаем
fmt.Printf("Ошибка сети! Код: %d, Текст: %s\n", netErr.Code, netErr.Message)
} else {
fmt.Println("Это не ошибка сети")
}
}
Вывод:
Ошибка сети! Код: 404, Текст: Not Found
👉 Что произошло?
errors.Asпроверила, чтоerrимеет тип*NetworkError.- Если да — сохранила её в
netErr, и мы можем использовать её поля (Code,Message).
📌 Пример 2: Ошибка обёрнута в другую ошибку
Часто ошибки “заворачивают” в другие ошибки с помощью fmt.Errorf и %w. As умеет “пробираться” через такие обёртки.
func fetchData() error {
return &NetworkError{Code: 500, Message: "Server Error"}
}
func main() {
err := fetchData()
wrappedErr := fmt.Errorf("не удалось получить данные: %w", err) // оборачиваем
var netErr *NetworkError
if errors.As(wrappedErr, &netErr) { // всё равно находит NetworkError внутри!
fmt.Printf("Ошибка сети: %d - %s\n", netErr.Code, netErr.Message)
}
}
Вывод:
Ошибка сети: 500 - Server Error
👉 Почему это полезно?
Даже если ошибка была обёрнута (%w), As “достанет” её исходный тип.
📌 Пример 3: Проверка нескольких типов ошибок
Иногда ошибка может быть одного из нескольких типов. As позволяет это проверить.
type TimeoutError struct {
TimeoutSec int
}
func (e *TimeoutError) Error() string {
return fmt.Sprintf("Timeout after %d sec", e.TimeoutSec)
}
func main() {
err := &TimeoutError{TimeoutSec: 30} // допустим, это наша ошибка
// Пробуем разные типы:
var timeoutErr *TimeoutError
var networkErr *NetworkError
if errors.As(err, &timeoutErr) {
fmt.Println("Это TimeoutError:", timeoutErr.TimeoutSec)
}
if errors.As(err, &networkErr) {
fmt.Println("Это NetworkError:", networkErr.Code)
} else {
fmt.Println("Это не NetworkError") // сработает это
}
}
Вывод:
Это TimeoutError: 30
Это не NetworkError
👉 Вывод:As проверяет по очереди каждый тип. Если ошибка не соответствует типу — просто возвращает false.
📌 Пример 4: Работа со стандартными ошибками
As можно использовать и со встроенными типами ошибок, например, с *os.PathError.
func main() {
_, err := os.Open("non-existent-file.txt") // вернёт PathError
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Printf("Ошибка файла: %s (операция: %s)\n", pathErr.Path, pathErr.Op)
}
}
Вывод (примерно):
Ошибка файла: non-existent-file.txt (операция: open)
🎯 Итог: Когда использовать As?
- Если нужно проверить тип ошибки (например,
MyCustomError). - Если нужно достать поля ошибки (например,
err.Code,err.TimeoutSec). - Если ошибка может быть обёрнута (
%w), но тебе нужна исходная.
❌ As vs Is
Is→ проверяет конкретное значение ошибки (err == io.EOF).As→ проверяет тип ошибки (err— это*MyError?).
func As(err error, target any) bool
As находит первую ошибку в дереве err, которая совпадает с target, и если такая найдена, устанавливает target в значение этой ошибки и возвращает true. В противном случае возвращает false.
Дерево состоит из err самой по себе, за которой следуют ошибки, полученные путем многократного вызова ее метода Unwrap() error или Unwrap() []error. Когда err оборачивает несколько ошибок, As проверяет err, за которой следует глубинный обход ее потомков.
Ошибка совпадает с target, если конкретное значение ошибки может быть присвоено значению, на которое указывает target, или если у ошибки есть метод As(any) bool, такой что As(target) возвращает true. В последнем случае метод As отвечает за установку target.
Тип ошибки может предоставить метод As, чтобы его можно было рассматривать как если бы он был другим типом ошибки.
As вызывает панику, если target не является не nil указателем на тип, реализующий error, или на любой интерфейсный тип.
Пример
package main
import (
"errors"
"fmt"
"io/fs"
"os"
)
func main() {
if _, err := os.Open("non-existing"); err != nil {
var pathError *fs.PathError
if errors.As(err, &pathError) {
fmt.Println("Failed at path:", pathError.Path)
} else {
fmt.Println(err)
}
}
}
Output:
Failed at path: non-existingfunc Is
Объяснение Is и As
В Go пакет errors предоставляет функции Is и As для работы с ошибками. Они помогают сравнивать ошибки и проверять их тип. Давай разберём их простым языком.
errors.Is – проверка на конкретную ошибку
Что делает?
Проверяет, содержит ли цепочка ошибок (error) конкретную ошибку.
Аналог из жизни:
Представь, что у тебя есть коробка с вложенными коробками (ошибка, которая обёрнута в другие ошибки). Ты ищешь конкретную вещь (например, ключ). errors.Is рекурсивно открывает все коробки и проверяет, есть ли там нужный ключ.
Пример:
var ErrNotFound = errors.New("not found")
err := fmt.Errorf("ошибка: %w", ErrNotFound) // оборачиваем ошибку
if errors.Is(err, ErrNotFound) {
fmt.Println("Ошибка 'not found' найдена!")
}
Вывод:
Ошибка 'not found' найдена!
Здесь errors.Is “распаковала” err и обнаружила внутри ErrNotFound.
errors.As – проверка типа ошибки
Что делает?
Проверяет, можно ли привести ошибку к определённому типу (например, к твоей кастомной структуре-ошибке). Если да – сохраняет её в переменную.
Аналог из жизни:
У тебя есть коробка, в которой может лежать либо книга, либо ручка. Ты говоришь: “Если там книга, положи её на стол”. errors.As проверяет тип и “кладёт” ошибку в переменную, если тип подходит.
Пример:
type MyError struct {
Code int
Msg string
}
err := &MyError{Code: 404, Msg: "Not Found"} // создаём ошибку типа *MyError
var myErr *MyError
if errors.As(err, &myErr) { // передаём указатель на переменную
fmt.Printf("Код: %d, Сообщение: %s\n", myErr.Code, myErr.Msg)
}
Вывод:
Код: 404, Сообщение: Not Found
Здесь errors.As проверила, что err имеет тип *MyError, и сохранила её в myErr.
Разница между Is и As
| Функция | Для чего? | Работает с |
|---|---|---|
Is | Проверить, что ошибка именно X | Конкретными ошибками (==) |
As | Проверить, что ошибка типа X | Кастомными типами ошибок (структуры) |
Когда использовать?
Is– если нужно проверить, что ошибка равна конкретному значению (например,io.EOF).As– если нужно проверить, что ошибка имеет определённый тип, и достать её поля.
Обе функции поддерживают обёрнутые ошибки (с %w в fmt.Errorf), поэтому они умеют “заглядывать” внутрь цепочек ошибок. 🚀
func Is(err, target error) bool
Is сообщает, совпадает ли какая-либо ошибка в дереве err с target.
Дерево состоит из err самой по себе, за которой следуют ошибки, полученные путем многократного вызова ее метода Unwrap() error или Unwrap() []error. Когда err оборачивает несколько ошибок, Is проверяет err, за которой следует глубинный обход ее потомков.
Ошибка считается совпадающей с целевой, если она равна этой целевой или если она реализует метод Is(error) bool, такой что Is(target) возвращает true.
Тип ошибки может предоставить метод Is, чтобы его можно было рассматривать как эквивалентный существующей ошибке. Например, если MyError определяет
func (m MyError) Is(target error) bool { return target == fs.ErrExist }
то Is(MyError{}, fs.ErrExist) возвращает true. См. syscall.Errno.Is для примера в стандартной библиотеке. Метод Is должен только поверхностно сравнивать err и цель и не вызывать Unwrap для обоих.
Пример
package main
import (
"errors"
"fmt"
"io/fs"
"os"
)
func main() {
if _, err := os.Open("non-existing"); err != nil {
if errors.Is(err, fs.ErrNotExist) {
fmt.Println("file does not exist")
} else {
fmt.Println(err)
}
}
}
Output:
file does not existfunc Join
func Join(errs ...error) error
Join возвращает ошибку, которая оборачивает переданные ошибки. Любые значения nil ошибки отбрасываются. Join возвращает nil, если все значения в errs равны nil. Ошибка форматируется как конкатенация строк, полученных путем вызова метода Error каждого элемента errs, с новой строкой между каждой строкой.
Не nil ошибка, возвращенная Join, реализует метод Unwrap() []error.
Пример:
package main
import (
"errors"
"fmt"
)
func main() {
err1 := errors.New("first error")
err2 := errors.New("second error")
err3 := errors.New("third error")
combinedErr := errors.Join(err1, err2, err3)
fmt.Println(combinedErr)
}
func New
func New(text string) error
New возвращает ошибку, которая форматируется как заданный текст. Каждый вызов New возвращает уникальное значение ошибки, даже если текст идентичен.
Пример:
package main
import (
"errors"
"fmt"
)
func main() {
err := errors.New("something went wrong")
fmt.Println(err)
}
Пример
package main
import (
"fmt"
)
func main() {
const name, id = "bimmler", 17
err := fmt.Errorf("user %q (id %d) not found", name, id)
if err != nil {
fmt.Print(err)
}
}
Output:
user "bimmler" (id 17) not found
func Unwrap
func Unwrap(err error) error
Unwrap возвращает основную ошибку, обернутую в err, если она существует. Если err не оборачивает никакую ошибку, Unwrap возвращает nil.
Пример
package main
import (
"errors"
"fmt"
)
func main() {
err1 := errors.New("error1")
err2 := fmt.Errorf("error2: [%w]", err1)
fmt.Println(err2)
fmt.Println(errors.Unwrap(err2))
}
Output:
error2: [error1]
error114 - Описание пакета iter языка программирования Go
Итераторы
Итераторы
Итератор — это функция, которая передает последовательные элементы последовательности в функцию обратного вызова, традиционно называемую yield. Функция останавливается либо когда последовательность завершена, либо когда yield возвращает false, указывая на необходимость прекратить итерацию раньше. Этот пакет определяет Seq и Seq2 (произносится как seek — первая слог последовательности) как сокращения для итераторов, которые передают 1 или 2 значения на элемент последовательности в yield:type (
Seq[V any] func(yield func(V) bool)
Seq2[K, V any] func(yield func(K, V) bool)
)
Seq2 представляет последовательность пар значений, традиционно ключ-значение или индекс-значение.
Yield возвращает true, если итератор должен продолжить с следующим элементом последовательности, и false, если он должен остановиться.
Например, maps.Keys возвращает итератор, который производит последовательность ключей карты m, реализованную следующим образом:
func Keys[Map ~map[K]V, K comparable, V any](m Map) iter.Seq[K] {
return func(yield func(K) bool) {
for k := range m {
if !yield(k) {
return
}
}
}
}
Дополнительные примеры можно найти в блоге Go: Range Over Function Types.
Функции итераторов чаще всего вызываются в цикле range, как в следующем примере:
func PrintAll[V any](seq iter.Seq[V]) {
for v := range seq {
fmt.Println(v)
}
}
Конвенции именования
Функции и методы итераторов называются в зависимости от последовательности, по которой они проходят:
// All возвращает итератор по всем элементам в s.
func (s *Set[V]) All() iter.Seq[V]
Метод итератора на типе коллекции традиционно называется All, потому что он итерирует последовательность всех значений в коллекции.
Для типа, содержащего несколько возможных последовательностей, имя итератора может указывать, какая последовательность предоставляется:
// Cities возвращает итератор по основным городам страны.
func (c *Country) Cities() iter.Seq[*City]
// Languages возвращает итератор по официальным языкам страны.
func (c *Country) Languages() iter.Seq[string]
Если итератор требует дополнительной конфигурации, конструкторная функция может принимать дополнительные аргументы конфигурации:
// Scan возвращает итератор по парам ключ-значение с min ≤ key ≤ max.
func (m *Map[K, V]) Scan(min, max K) iter.Seq2[K, V]
// Split возвращает итератор по (возможно, пустым) подстрокам s,
// разделенным sep.
func Split(s, sep string) iter.Seq[string]
Когда существует несколько возможных порядков итерации, имя метода может указывать на этот порядок:
// All возвращает итератор по списку от начала к концу.
func (l *List[V]) All() iter.Seq[V]
// Backward возвращает итератор по списку от конца к началу.
func (l *List[V]) Backward() iter.Seq[V]
// Preorder возвращает итератор по всем узлам синтаксического дерева
// под (и включая) указанный корень, в глубину, в порядке preorder,
// посещая родительский узел перед его дочерними узлами.
func Preorder(root Node) iter.Seq[Node]
Одноразовые итераторы
Большинство итераторов предоставляют возможность пройти по всей последовательности: при вызове итератор выполняет любую необходимую настройку для начала последовательности, затем вызывает yield для последовательных элементов последовательности и затем очищает перед возвратом. Повторный вызов итератора снова проходит по последовательности.
Некоторые итераторы нарушают это правило, предоставляя возможность пройти по последовательности только один раз. Эти “одноразовые итераторы” обычно сообщают значения из потока данных, который нельзя перемотать назад для начала. Повторный вызов итератора после ранней остановки может продолжить поток, но повторный вызов после завершения последовательности не вернет никаких значений. Документационные комментарии для функций или методов, возвращающих одноразовые итераторы, должны документировать этот факт:
// Lines возвращает итератор по строкам, прочитанным из r.
// Он возвращает одноразовый итератор.
func (r *Reader) Lines() iter.Seq[string]
Извлечение значений
Функции и методы, которые принимают или возвращают итераторы, должны использовать стандартные типы Seq или Seq2, чтобы обеспечить совместимость с циклами range и другими адаптерами итераторов. Стандартные итераторы можно рассматривать как “push-итераторы”, которые передают значения в функцию yield.
Иногда цикл range не является наиболее естественным способом потребления значений последовательности. В этом случае Pull преобразует стандартный push-итератор в “pull-итератор”, который можно вызвать для извлечения одного значения за раз из последовательности. Pull запускает итератор и возвращает пару функций — next и stop, которые возвращают следующее значение из итератора и останавливают его соответственно.
Например:
// Pairs возвращает итератор по последовательным парам значений из seq.
func Pairs[V any](seq iter.Seq[V]) iter.Seq2[V, V] {
return func(yield func(V, V) bool) {
next, stop := iter.Pull(seq)
defer stop()
for {
v1, ok1 := next()
if !ok1 {
return
}
v2, ok2 := next()
// Если ok2 равно false, v2 должно быть
// нулевым значением; yield одна последняя пара.
if !yield(v1, v2) {
return
}
if !ok2 {
return
}
}
}
}
Если клиенты не потребляют последовательность до конца, они должны вызвать stop, что позволяет функции итератора завершить выполнение и вернуться. Как показано в примере, традиционный способ обеспечить это — использовать defer.
Использование стандартной библиотеки
Некоторые пакеты в стандартной библиотеке предоставляют API