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: 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
