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 integerp := &i
— p is a pointer to variable i
Initial Steps Overview
-
Check if the pointer is being set
-
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
Здравствуйте! Помогите пожалуйста разобраться в проблеме.
Используя фреймворк 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) } |