Runtime error invalid memory address or nil pointer dereference goroutine 6

panic: runtime error: invalid memory address or nil pointer dereference|

Invalid Memory Address or Nil Pointer Dereference

Overview

Example error:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x497783]

This issue happens when you reference an unassigned (nil) pointer.

Go uses an asterisk * to specify a pointer to a variable, and an ampersand & to generate a pointer to a given variable. For example:

  • var p *int — p is a pointer to a type integer
  • p := &i — p is a pointer to variable i

Initial Steps Overview

  1. Check if the pointer is being set

  2. Check for a nil assignment to the pointer

Detailed Steps

1) Check if the pointer is being set

Before a pointer can be referenced, it needs to have something assigned to it.

type person struct {
	Name string
	Age  int
}

func main() {
	var myPointer *person
	fmt.Println(myPointer.Name)
}
$ go run main.go
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x497783]

The above code causes Go to panic because, while myPointer has been declared as a pointer to a person object, it currently does not point to a particular instance of such an object. This can be solved by assigning a variable to the pointer (Solution A) or by creating and referencing a new object (Solution B).

2) Check for a nil assignment to the pointer

Another possibility is that the pointer is being set to nil somewhere in your code. This could be, for example, a function returning nil after failing to accomplish a task.

type NumberObject struct {
	number int
}

func createNumberObj(num int) (result *NumberObject) {
	// Returns a pointer to a NumberObject if the number is allowed, otherwise returns nil
	if num < 100 {
		numberObj := NumberObject{num}
		return &numberObj // Returns a reference to the new object
	} else {
		return nil
	}
}

func main() {
	myNumberObject := createNumberObject(101)
	fmt.Println(myNumberObject.number) // This will cause Go to panic!
}

Here a NumberObject is created only if the input is less than 100. A pointer is returned by the function either way, but the main() function is not prepared to handle the nil pointer that is returned if the conditions aren’t met, causing Go to panic. It would be best to handle this issue by either handling the nil pointer as demonstrated in Solution B, or by creaing and referencing an empty object as per Solution C.

Solutions List

A) Assign a variable to the pointer

B) Handle the nil pointer

C) Create and reference a new variable

Solutions Detail

A) Assign a variable to the pointer

type Person struct{
	Name string
	Age int
}

func main()
	var myPointer *Person // This is currently a nil pointer

	aPerson := Person{
		Name: "Joey",
		Age:  29,
	} // This is a Person object

	myPointer = &aPerson // Now the pointer references the same Person as 'aPerson'

	fmt.Println(joey.Name)
}

Here the variable aPerson is created, after which we can use the Go & syntax to get this variable’s reference (location in memory) and assign it to myPointer. Importantly, both myPointer and aPerson now point to the same variable in memory, and modifications made to either will apply to both.

func main()
	var myPointer *person
	myPointer = &anotherPerson

	anotherPerson.Name = "Pete"

	fmt.Println(myPointer.Name) // This will print "Pete"!
}

B) Handle the nil pointer

Going back to the code used in Step 2, we can expand this to check for and ‘handle’ a nil value before the code continues.

type NumberObject struct {
	number int
}

func createNumberObj(num int) (result *NumberObject) {
	// Returns a pointer to a NumberObject if the number is allowed, otherwise returns nil
	if num < 100 {
		numberObj := NumberObject{num}
		return &numberObj // Returns a reference to the new object
	} else {
		return nil
	}
}

func main() {
	myNumberObject := createNumberObject(101)

	if myNumberObject == nil {
		log.Fatal("Failed to create number object!")
	}

	fmt.Println(myNumberObject.number) // This line is not reached if num >= 100!
}

There are several ways this situation could be handled; in this particular instance the log.Fatal function will terminate the program if myNumberObject is a nil pointer. Another option would be to return an error in the createNumberObj function informing the user that the inputted number was too high. A further option, and one which would allow the program to continute executing, would be to create and reference a new variable, as seen in Solution C.

C) Create and reference a new variable

type NumberObject struct {
	number int
}

func createNumberObj(num int) (result *NumberObject) {
	numberObj := NumberObject{} // Creates a new, empty NumberObject
	if num < 100 {
		numberObj.number = num
	}
	return &numberObj // Returns a reference to the new object
}

func main() {
	myNumberObject := createNumberObject(101)

	fmt.Println(myNumberObject.number) // Will print 0 instead of causing a panic!
}

Here we have restructured the program to first create a new variable, and then edit the properties of this object as the program executes, removing the risk of the function returning a nil pointer. In the event it is not explicitly set, the value of NumberObject.number will default to 0, rather than nil as you might expect — this is because Go has default ‘zero values’ for all it’s types, which you can read more about here.

Further Information

Gotcha Nil Pointer Dereference

Golang default zero values

Go Pointers

Owner

Joey

comments powered by

Здравствуйте! Помогите пожалуйста разобраться в проблеме.

Используя фреймворк IRIS написал REST службу. Использую 1.11.5 версию Golang. При обращении к определенному URL пытаюсь вернуть все данные из таблицы reports (база данных PostgreSQL) c помощью библиотеки GORM. В момент запроса к базе данных возникает ошибка:

[WARN] 2019/06/06 13:31 Recovered from a route's Handler('constructor/controllers.glob..func1')
At Request: 200 /api/report/d5f9c639-13e6-42c1-9043-30783981724b GET ::1
Trace: runtime error: invalid memory address or nil pointer dereference

C:/Go/src/runtime/asm_amd64.s:522
C:/Go/src/runtime/panic.go:513
C:/Go/src/runtime/panic.go:82
C:/Go/src/runtime/signal_windows.go:204
C:/Users/NNogerbek/go/src/github.com/jinzhu/gorm/main.go:768
C:/Users/NNogerbek/go/src/github.com/jinzhu/gorm/main.go:179
C:/Users/NNogerbek/go/src/github.com/jinzhu/gorm/main.go:322
C:/Users/NNogerbek/go/src/constructor/controllers/report.go:14
C:/Users/NNogerbek/go/src/github.com/kataras/iris/context/context.go:1208
C:/Users/NNogerbek/go/src/github.com/kataras/iris/context/context.go:1217
C:/Users/NNogerbek/go/src/github.com/kataras/iris/middleware/logger/logger.go:50
C:/Users/NNogerbek/go/src/github.com/kataras/iris/middleware/logger/logger.go:31
C:/Users/NNogerbek/go/src/github.com/kataras/iris/context/context.go:1208
C:/Users/NNogerbek/go/src/github.com/kataras/iris/context/context.go:1217
C:/Users/NNogerbek/go/src/github.com/kataras/iris/middleware/recover/recover.go:56
C:/Users/NNogerbek/go/src/github.com/kataras/iris/context/context.go:922
C:/Users/NNogerbek/go/src/github.com/kataras/iris/context/context.go:1094
C:/Users/NNogerbek/go/src/github.com/kataras/iris/core/router/handler.go:227
C:/Users/NNogerbek/go/src/github.com/kataras/iris/core/router/router.go:84
C:/Users/NNogerbek/go/src/github.com/kataras/iris/core/router/router.go:161
C:/Go/src/net/http/server.go:2741
C:/Go/src/net/http/server.go:1847
C:/Go/src/runtime/asm_amd64.s:1333

Ниже представлен полный код проекта. Подскажите пожалуйста в каком месте сделал упущение?

main.go:

package main

import (
	"constructor/database"
	"constructor/routes"
	"github.com/joho/godotenv"
	"github.com/kataras/iris"
)

func main()  {
	// Check the connection to remote PostgreSQL database with the help of the "gorm".
	database.ConnectPostgreSQL()
	defer database.DisconnectPostgreSQL()

	// Create an application with default logger and recovery middleware.
	application := iris.Default()

	// Define the list of all available routes of the application.
	routes.Handle(application)

	// Start to listen and serve the application.
	application.Run(iris.Addr(":8000" + port), iris.WithoutServerError(iris.ErrServerClosed))
}

database.go:

package database

import (
	"constructor/utils"
	"fmt"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/postgres"
)

var DBGORM *gorm.DB

func ConnectPostgreSQL() {
	// Initialize variables related with the remote PostgreSQL database.
	databaseUser := utils.CheckEnvironmentVariable("PostgreSQL_USER")
	databasePassword := utils.CheckEnvironmentVariable("PostgreSQL_PASSWORD")
	databaseHost := utils.CheckEnvironmentVariable("PostgreSQL_HOST")
	databaseName := utils.CheckEnvironmentVariable("PostgreSQL_DATABASE_NAME")

	// Define the connection string for the remote PostgreSQL database with the help of the "gorm" package.
	databaseURL:= fmt.Sprintf("host=%s user=%s dbname=%s password=%s sslmode=disable", databaseHost, databaseUser, databaseName, databasePassword)

	// Create connection pool to remote PostgreSQL database with the help of the "gorm" package.
	DBGORM, err := gorm.Open("postgres", databaseURL); if err != nil {
		utils.Logger().Println(err)
		panic(err)
	}

	// Ping the remote PostgreSQL database with the help of "gorm" package.
	err = DBGORM.DB().Ping(); if err != nil {
		utils.Logger().Println(err)
		panic(err)
	}

	// Enable logging mode of "gorm" package.
	DBGORM.LogMode(true)
}

func DisconnectPostgreSQL() error {
	return DBGORM.Close()
}

routes.go:

package routes

import (
	"constructor/controllers"
	"github.com/kataras/iris"
)

func Handle(application *iris.Application)  {
	application.Get("/api/report/{report_id:string}", controllers.GetReport)
}

controllers/report.go:

package controllers
import (
	"constructor/database"
	"constructor/utils"
	"github.com/kataras/iris"
)

type Report struct {
	ID int `gorm:"primary_key" json:"report_id"`
	Name string `json:"report_name"`
}

var GetReport = func(ctx iris.Context) {
	// Initialize variable.
	reportID := ctx.Params().Get("report_id")

	if utils.IsValidUUID(reportID) {
		var reports []Report
		database.DBGORM.Find(&reports) // <-ERROR
		ctx.JSON(reports)
	} else {
		ctx.StatusCode(iris.StatusBadRequest)
		ctx.JSON(iris.Map{"STATUS": "ERROR", "DESCRIPTION": "BAD REQUEST",})
	}
}

//The file /Users/jinke/golang/src/cds_spider/common/dbpool/mysql.go

package dbpool

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
)

var DB *sql.DB

func init() {
    db, err := sql.Open("mysql", "root:123456@tcp(192.168.1.20:3306)/?charset=utf8")
    if err != nil {
        panic("dbpool init >> " + err.Error())
    }
    DB = db
    DB.SetMaxIdleConns(5)
}

//The file /Users/jinke/golang/src/cds_spider/newCar/koubei/koubei.go

package koubei

import (
    "cds_spider/common/dbpool"
)

type KouBei struct {
    Auto_koubei_id                                               int
    Source                                                       string
    Auto_brand_id, Auto_company_id, Auto_serial_id, Auto_type_id int
    Username                                                     string
    Price                                                        float64
    Province_id, City_id                                         int
    Buy_date, Content, Spider_url, Created                       string
    Level                                                        int
}

func (k *KouBei) Save() (insertID int64, err error) {
    stmt, err := dbpool.DB.Prepare("INSERT mains.koubei SET source=?, auto_brand_id=?, auto_company_id=?, auto_serial_id=?, auto_type_id=?, username=?, price=?, province_id=?, city_id=?, buy_date=?, content=?, level=?, spider_url=?")
    if err != nil {
        return 0, err
    }
    defer stmt.Close()

    res, err := stmt.Exec(k.Source, k.Auto_brand_id, k.Auto_company_id, k.Auto_serial_id, k.Auto_type_id, k.Username, k.Price, k.Province_id, k.City_id, k.Buy_date, k.Content, k.Level, k.Spider_url)
    if err != nil {
        return 0, err
    }
    return res.LastInsertId()
}

«stmt, err := dbpool.DB.Prepare» is Error invalid memory address or nil pointer dereference

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x41 pc=0x52fb5c]

goroutine 2208 [running]:
github.com/go-sql-driver/mysql.(*mysqlConn).writeCommandPacketUint32(0x0, 0x1048eca19, 0xc2046b8330, 0x2ba32ae35a88)
    /Users/jinke/golang/src/github.com/go-sql-driver/mysql/packets.go:327 +0x1c
github.com/go-sql-driver/mysql.(*mysqlStmt).Close(0xc200e3ba50, 0x0, 0x0)
    /Users/jinke/golang/src/github.com/go-sql-driver/mysql/statement.go:24 +0x46
database/sql.(*driverConn).finalClose(0xc2020d5cc0, 0xc20370d900, 0x2bbc7f0)
    /usr/local/go/src/pkg/database/sql/sql.go:285 +0x87
database/sql.func·002(0xc2000ca200, 0xc2000aebd0)
    /usr/local/go/src/pkg/database/sql/sql.go:372 +0x2c
database/sql.(*driverConn).Close(0xc2020d5cc0, 0x3, 0x4a173d)
    /usr/local/go/src/pkg/database/sql/sql.go:278 +0x174
database/sql.(*DB).putConn(0xc2000ca1e0, 0xc2020d5cc0, 0x0, 0x0)
    /usr/local/go/src/pkg/database/sql/sql.go:598 +0x2e2
database/sql.(*DB).prepare(0xc2000ca1e0, 0x742450, 0xd2, 0xc20035ba50, 0x85, ...)
    /usr/local/go/src/pkg/database/sql/sql.go:640 +0x267
database/sql.(*DB).Prepare(0xc2000ca1e0, 0x742450, 0xd2, 0x4b2488, 0xc2000ae140, ...)
    /usr/local/go/src/pkg/database/sql/sql.go:608 +0x5a
cds_spider/newCar/koubei.(*KouBei).Save(0xc2046ab790, 0x0, 0x0, 0x0)
    /Users/jinke/golang/src/cds_spider/newCar/koubei/koubei.go:20 +0x6b

2 декабря, 2019 11:54 дп
681 views
| Комментариев нет

Development

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

Читайте также: Обработка ошибок в Go

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

Что такое паника?

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

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

Паника вне границ

Когда вы пытаетесь получить доступ к индексу, который превышает длину фрагмента или емкость массива, среда выполнения Go вызывает панику.

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

package main
import (
"fmt"
)
func main() {
names := []string{
"lobster",
"sea urchin",
"sea cucumber",
}
fmt.Println("My favorite sea creature is:", names[len(names)])
}

Это вернет такой вывод:

panic: runtime error: index out of range [3] with length 3
goroutine 1 [running]:
main.main()
/tmp/sandbox879828148/prog.go:13 +0x20

Вывод паники содержит подсказку:

panic: runtime error: index out of range

Мы создали срез из трех элементов. Затем мы попытались получить последний элемент среза, проиндексировав этот срез по длине с помощью встроенной функции len. Помните, что срезы и массивы начинаются с нуля; поэтому первый элемент в этом срезе имеет индекс 0, а последний элемент – индекс 2. Поскольку команда обнаружила 3 элемента, она пытается получить доступ к элементу по индексу 3, но в срезе нет такого элемента – он находится за границами среза. Среда выполнения не может ничего сделать, только завершить работу и выйти, поскольку ей предложили сделать невозможное. Кроме того, Go не может обнаружить эту ошибку во время компиляции.

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

Компоненты паники

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

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

panic: runtime error: index out of range [3] with length 3

Строка «runtime error:» после префикса panic сообщает, что паника была сгенерирована средой выполнения языка. Эта паника говорит, что мы попытались использовать индекс [3], который был вне диапазона среза.

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

goroutine 1 [running]:
main.main()
/tmp/sandbox879828148/prog.go:13 +0x20

Эта трассировка из предыдущего примера показывает, что наша программа сгенерировала панику из файла /tmp/sandbox879828148/prog.go в строке под номером 13. Она также говорит, что паника была сгенерирована в функции main() из основного пакет.

Трассировка стека разбивается на отдельные блоки – по одному для каждой процедуры goroutine в программе. Обработка операций каждой программы Go выполняется одной или несколькими процедурами, каждая из которых может независимо и одновременно выполнять части вашего кода Go. Каждый блок начинается с заголовка goroutine X [state]:. В заголовке указан ID программы, а также состояние, в котором она находилась, когда возникла паника. После заголовка трассировка стека показывает функцию, которую программа выполняла, когда произошла паника, а также имя файла и номер строки, где выполнялась функция.

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

Нулевые указатели

Язык программирования Go поддерживает указатели для ссылки на конкретный экземпляр какого-либо типа, существующий в памяти компьютера во время выполнения. Указатели могут принимать значение nil, указывающее, что они ни на что не указывают. Когда мы пытаемся вызвать методы с нулевым указателем, среда выполнения Go сгенерирует панику. Точно так же переменные, которые являются типами интерфейса, будут вызывать панику при обращении к ним методов. Чтобы посмотреть на панику, возникающую в этих случаях, попробуйте запустить следующий пример:

package main
import (
"fmt"
)
type Shark struct {
Name string
}
func (s *Shark) SayHello() {
fmt.Println("Hi! My name is", s.Name)
}
func main() {
s := &Shark{"Sammy"}
s = nil
s.SayHello()
}

Паника вернет такой вывод:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xdfeba]
goroutine 1 [running]:
main.(*Shark).SayHello(...)
/tmp/sandbox160713813/prog.go:12
main.main()
/tmp/sandbox160713813/prog.go:18 +0x1a

В этом примере мы определили структуру под названием Shark. У Shark есть один метод, определенный в приемнике указателя, который называется SayHello, он при вызове выводит приветствие на стандартный вывод. В теле функции main мы создаем новый экземпляр этой структуры Shark и запрашиваем указатель на нее с помощью оператора &. Этот указатель присваивается переменной s. Затем мы присваиваем переменной s значение nil с помощью оператора s = nil. После этого мы пытаемся вызвать метод SayHello для переменной s. Вместо ожидаемого сообщения мы получаем панику, так как мы попытались получить доступ к неверному адресу памяти. Поскольку переменная s равна nil, при вызове функции SayHello она пытается получить доступ к полю Name в типе *Shark. Это приемник указателя, а указатель в этом случае равен нулю, и программа паникует, потому что не может разыменовать нулевой указатель.

В этом примере мы явно присвоили переменной s значение nil, а на практике это происходит не так очевидно. Когда вы видите панику, связанную с nil pointer dereference, убедитесь, что вы правильно присвоили все переменные указателей, которые есть у вас в программе.

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

Функция panic

Также вы можете сгенерировать собственную панику, используя встроенную функцию panic. В качестве аргумента она принимает одну строку – сообщение, которое будет выводить паника. Обычно это сообщение короче, чем код для возврата ошибки. Кроме того, панику можно использовать это в собственных пакетах, чтобы указать разработчикам, что они могли ошибиться при использовании кода из вашего пакета. По возможности лучше возвращать значения error потребителям ваших пакетов.

Запустите этот код, чтобы увидеть панику, сгенерированную из функции, вызванной из другой функции:

package main
func main() {
foo()
}
func foo() {
panic("oh no!")
}

Вывод паники выглядит так:

panic: oh no!
goroutine 1 [running]:
main.foo(...)
/tmp/sandbox494710869/prog.go:8
main.main()
/tmp/sandbox494710869/prog.go:4 +0x40

Здесь мы определили функцию foo, которая вызывает встроенную функцию panic со строкой “oh no!”. Эта функция вызывается функцией main. Обратите внимание, на выходе появляется сообщение panic: oh no!, а трассировка стека показывает одну процедуру goroutine с двумя строками (одна для функции main(), вторая для функции foo()).

Итак, паника останавливает программу там, где она появляется. Это может создать проблемы, если у вас есть открытые ресурсы, которые следует надлежащим образом закрыть. Go предоставляет механизм для выполнения отдельных фрагментов кода даже при возникновении паники.

Отложенные функции

Ваша программа может иметь ресурсы, которые нужно очистить, даже если во время выполнения обрабатывается паника. Go позволяет отложить выполнение вызова функции до тех пор, пока вызывающая функция не завершит работу. Отложенные функции работают даже при возникновении паники. По сути они используются в качестве защитного механизма от хаотического характера паник. Функции откладываются путем их обычного вызова, а затем для всего оператора ставится префикс с ключевым словом defer (например, defer sayHello()). Запустите этот пример, чтобы посмотреть, как программа выведет сообщение даже после возникновения паники:

package main
import "fmt"
func main() {
defer func() {
fmt.Println("hello from the deferred function!")
}()
panic("oh no!")
}

Результат в этом примере будет выглядеть так:

hello from the deferred function!
panic: oh no!
goroutine 1 [running]:
main.main()
/Users/gopherguides/learn/src/github.com/gopherguides/learn//handle-panics/src/main.go:10 +0x55

В этом примере в рамках функции main мы сначала откладываем вызов анонимной функции, которая выводит сообщение “hello from the deferred function!”. Затем функция main немедленно производит панику, используя функцию panic. В выходных данных этой программы мы увидим, что отложенная функция выполняется и выводит свое сообщение.

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

Обработка паники

У паники есть единый механизм восстановления – встроенная функция recover. Эта функция позволяет перехватить панику на пути вверх по стеку вызовов и предотвратить неожиданное завершение программы. Она работает только по строгим правилам, но имеет важнейшее значение в среде производства.

Поскольку функция recover является частью встроенного пакета builtin, recover можно вызвать без импорта каких-либо дополнительных пакетов:

package main
import (
"fmt"
"log"
)
func main() {
divideByZero()
fmt.Println("we survived dividing by zero!")
}
func divideByZero() {
defer func() {
if err := recover(); err != nil {
log.Println("panic occurred:", err)
}
}()
fmt.Println(divide(1, 0))
}
func divide(a, b int) int {
return a / b
}

Этот пример вернет:

2009/11/10 23:00:00 panic occurred: runtime error: integer divide by zero
we survived dividing by zero!

Функция main в этом примере вызывает функцию, которую мы определяем, divideByZero. В рамках этой функции мы откладываем вызов анонимной функции, отвечающей за работу со всеми паниками, которые могут возникнуть при выполнении divideByZero. В этой отложенной анонимной функции мы вызываем встроенную функцию recover и присваиваем переменной ошибку, которую она возвращает. Если divideByZero вызывает панику, это значение error будет установлено, в противном случае оно будет равно нулю (nil). Сравнивая переменную err с nil, мы можем определить, возникла ли паника. В таком случае мы регистрируем панику, используя функцию log.Println (как с любой другой ошибкой).

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

В выходных данных этого примера мы сначала увидим сообщение лога от анонимной функции, которая восстанавливает панику. После него будет сообщение we survived dividing by zero!. Мы добились этого благодаря встроенной функции recover, которая остановила катастрофическую панику – иначе паника привела бы к завершению работы программы Go.

Значение err, возвращаемое функцией recover() – это то же значение, которое было предоставлено для вызова функции panic(). Поэтому очень важно, чтобы значение err было равно nil, если паника не возникла.

Обнаружение паники с помощью функции recover

Функция recover полагается на значение ошибки, чтобы определить, произошла паника или нет. Поскольку аргумент функции panic является пустым интерфейсом, он может быть любого типа. Нулевое значение для интерфейса любого типа, включая пустой интерфейс, это nil. Здесь необходимо проявлять осторожность, чтобы избежать nil в качестве аргумента для panic, как показано в этом примере:

package main
import (
"fmt"
"log"
)
func main() {
divideByZero()
fmt.Println("we survived dividing by zero!")
}
func divideByZero() {
defer func() {
if err := recover(); err != nil {
log.Println("panic occurred:", err)
}
}()
fmt.Println(divide(1, 0))
}
func divide(a, b int) int {
if b == 0 {
panic(nil)
}
return a / b
}

Этот код выведет:

we survived dividing by zero!

Этот пример почти такой же, как предыдущий, с некоторыми небольшими изменениями. Функция divide была изменена: теперь она должна проверить, равен ли ее делитель b нулю. Если это так, то она сгенерирует панику, используя встроенную функцию panic с аргументом nil. Выходные данные теперь не включают в себя сообщение лога, показывающее, что возникла паника (даже если она была создана функцией divide). Именно из-за этого замалчивания очень важно убедиться, что аргумент встроенной функции panic  не равен nil.

Заключение

Мы рассмотрели несколько путей возникновения паники в Go и узнали, как восстановить работу с помощью встроенной функции recover. Самостоятельно использовать функцию panic можно не всегда, но правильное восстановление после паники является важным шагом для подготовки приложений Go к работе.

Tags: Go, Golang

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package main
 
import (
    "fmt"
    "go_blog/models"
    "net/http"
    "text/template"
)
//здесь хранятся данные формы
var posts map[string]*models.Post
 
//функция индексации главной страницы
func indexHandler(w http.ResponseWriter, r *http.Request) {
    t, err := template.ParseFiles("templates/index.html", "templates/header.html", "templates/footer.html")
    if err != nil {
        fmt.Fprintf(w, err.Error())
        return
    }
    fmt.Println(posts)
    t.ExecuteTemplate(w, "index", posts)
}
 
//функция индексации формы 
func writeHandler(w http.ResponseWriter, r *http.Request) {
    t, err := template.ParseFiles("templates/write.html", "templates/header.html", "templates/footer.html")
    if err != nil {
        fmt.Fprintf(w, err.Error())
        return
    }
 
    t.ExecuteTemplate(w, "write", nil)
}
//функция обработки формы редактированя
func editHandler(w http.ResponseWriter, r *http.Request) {
    t, err := template.ParseFiles("templates/write.html", "templates/header.html", "templates/footer.html")
    if err != nil {
        fmt.Fprintf(w, err.Error())
        return
    }
 
    id := r.FormValue("id")
    post, found := posts[id]
    if !found {
        http.NotFound(w, r)
    }
 
    t.ExecuteTemplate(w, "write", post)
}
 
//функция сохранения данных формы
func savePostHandler(w http.ResponseWriter, r *http.Request) {
    id := r.FormValue("id")
    title := r.FormValue("title")
    content := r.FormValue("content")
 
    var post *models.Post
    if id != "" {
        post = posts[id]
        post.Title = title
        post.Content = content
    } else {
        id = GenerateID()
        post := models.NewPost(id, title, content)
        posts[post.Id] = post
    }
 
    http.Redirect(w, r, "/", 302)
}
 
func main() {
    fmt.Println("Listening port: 3000")
    posts = make(map[string]*models.Post, 0)
 
    // /css/app.css
    http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("./assets"))))
    http.HandleFunc("/", indexHandler)
    http.HandleFunc("/write", writeHandler)
    http.HandleFunc("/edit", editHandler)
    http.HandleFunc("/SavePost", savePostHandler)
 
    http.ListenAndServe(":3000", nil)
}

Понравилась статья? Поделить с друзьями:
  • Runtime error index out of bounds как исправить
  • Runtime error как достать соседа
  • Runtime error википедия
  • Runtime error in setup script version dll
  • Runtime error yandex contest