Golang: Nil
В языке Go значение nil используется для обозначения отсутствия данных.
Оно является нулевым значением для типов, которые работают с указателями (указатели, срезы, карты, каналы, функции, интерфейсы). Рассмотрим примеры тех типов, которые мы уже изучили
nil для указателей
var ptr *int
fmt.Println(ptr == nil) // => trueПеременная указательного типа содержит nil, если не была явно инициализирована. Это означает, что она не указывает ни на какую область памяти.
Попытка разыменовать nil-указатель приводит к панике во время выполнения:
var ptr *int
fmt.Println(*ptr) // panic: invalid memory address or nil pointer dereferenceЧтобы избежать ошибки, перед разыменованием указателя выполняется проверка:
if ptr != nil {
fmt.Println(*ptr)
}nil в полях структур
Если структура содержит указательное поле, это поле по умолчанию будет nil, если его не инициализировать.
type User struct {
Name *string
}
func main() {
var u User
fmt.Println(u.Name == nil) // => true
}Такую структуру можно проверить перед использованием указателя в поле:
if u.Name != nil {
fmt.Println(*u.Name)
}Вызов методов на nil
Если метод вызывается на значении, равном nil, и этот метод не обращается к полям внутри объекта, то ошибка не возникает:
type Logger struct{}
func (l *Logger) Info() {
fmt.Println("Log info") // => Log info
}
func main() {
var l *Logger
l.Info() // работает, несмотря на то что l == nil
}Такое поведение разрешено в Go по дизайну. Аргумент l — это просто указатель на Logger, и метод может быть вызван даже на nil, если внутри метода нет обращения к данным. Это позволяет, например, реализовать "пустые" или "заглушки" (no-op реализации), где метод ничего не делает, но при этом вызывается безопасно.
Но если метод попытается обратиться к данным внутри структуры, произойдёт паника:
type User struct {
Name string
}
func (u *User) PrintName() {
fmt.Println(u.Name)
}
func main() {
var u *User
u.PrintName() // panic: invalid memory address or nil pointer dereference
}Вывод
nil — это значение по умолчанию для ссылочных типов, таких как указатели. Оно часто используется в Go и требует проверки перед обращением к данным.
Задание
Создайте структуру Config с полями:
Host— указатель на строку (*string),Port— число (int).
Реализуйте функцию PrintConfig(cfg *Config), которая должна:
-
Проверять, инициализировано ли поле
Host.- Если
Host == nil, выводить"Host is not set". - Если
Host != nil, выводить значение строкиHost.
- Если
-
Всегда выводить значение поля
Port.
Пример использования:
host := "localhost"
cfg := &Config{Host: &host, Port: 8080}
PrintConfig(cfg)
// Вывод:
// Host: localhost
// Port: 8080
cfg2 := &Config{Port: 3000}
PrintConfig(cfg2)
// Вывод:
// Host is not set
// Port: 3000Golang: Nil
В языке Go значение nil используется для обозначения отсутствия данных.
Оно является нулевым значением для типов, которые работают с указателями (указатели, срезы, карты, каналы, функции, интерфейсы). Рассмотрим примеры тех типов, которые мы уже изучили
nil для указателей
var ptr *int
fmt.Println(ptr == nil) // => trueПеременная указательного типа содержит nil, если не была явно инициализирована. Это означает, что она не указывает ни на какую область памяти.
Попытка разыменовать nil-указатель приводит к панике во время выполнения:
var ptr *int
fmt.Println(*ptr) // panic: invalid memory address or nil pointer dereferenceЧтобы избежать ошибки, перед разыменованием указателя выполняется проверка:
if ptr != nil {
fmt.Println(*ptr)
}nil в полях структур
Если структура содержит указательное поле, это поле по умолчанию будет nil, если его не инициализировать.
type User struct {
Name *string
}
func main() {
var u User
fmt.Println(u.Name == nil) // => true
}Такую структуру можно проверить перед использованием указателя в поле:
if u.Name != nil {
fmt.Println(*u.Name)
}Вызов методов на nil
Если метод вызывается на значении, равном nil, и этот метод не обращается к полям внутри объекта, то ошибка не возникает:
type Logger struct{}
func (l *Logger) Info() {
fmt.Println("Log info") // => Log info
}
func main() {
var l *Logger
l.Info() // работает, несмотря на то что l == nil
}Такое поведение разрешено в Go по дизайну. Аргумент l — это просто указатель на Logger, и метод может быть вызван даже на nil, если внутри метода нет обращения к данным. Это позволяет, например, реализовать "пустые" или "заглушки" (no-op реализации), где метод ничего не делает, но при этом вызывается безопасно.
Но если метод попытается обратиться к данным внутри структуры, произойдёт паника:
type User struct {
Name string
}
func (u *User) PrintName() {
fmt.Println(u.Name)
}
func main() {
var u *User
u.PrintName() // panic: invalid memory address or nil pointer dereference
}Вывод
nil — это значение по умолчанию для ссылочных типов, таких как указатели. Оно часто используется в Go и требует проверки перед обращением к данным.
Задание
Создайте структуру Config с полями:
Host— указатель на строку (*string),Port— число (int).
Реализуйте функцию PrintConfig(cfg *Config), которая должна:
-
Проверять, инициализировано ли поле
Host.- Если
Host == nil, выводить"Host is not set". - Если
Host != nil, выводить значение строкиHost.
- Если
-
Всегда выводить значение поля
Port.
Пример использования:
host := "localhost"
cfg := &Config{Host: &host, Port: 8080}
PrintConfig(cfg)
// Вывод:
// Host: localhost
// Port: 8080
cfg2 := &Config{Port: 3000}
PrintConfig(cfg2)
// Вывод:
// Host is not set
// Port: 3000Ваше упражнение проверяется по этим тестам
package solution
import (
"bytes"
"io"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPrintConfig(t *testing.T) {
tests := []struct {
name string
cfg *Config
expected string
}{
{
name: "With host",
cfg: &Config{Host: strPtr("localhost"), Port: 8080},
expected: "Host: localhost\nPort: 8080\n",
},
{
name: "Without host",
cfg: &Config{Port: 3000},
expected: "Host is not set\nPort: 3000\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Создаём pipe для перехвата вывода
r, w, err := os.Pipe()
assert.NoError(t, err)
stdout := os.Stdout
os.Stdout = w
// Выполняем тестируемую функцию
PrintConfig(tt.cfg)
// Закрываем writer и проверяем ошибку
closeErr := w.Close()
assert.NoError(t, closeErr)
// Восстанавливаем Stdout
os.Stdout = stdout
// Читаем перехваченный вывод
var buf bytes.Buffer
_, err = io.Copy(&buf, r)
assert.NoError(t, err)
// Проверка результата
assert.Equal(t, tt.expected, buf.String())
})
}
}
func strPtr(s string) *string {
return &s
}Решение учителя откроется через:
20:00
