[{"data":1,"prerenderedAt":3634},["ShallowReactive",2],{"page-\u002Fgo\u002F15-error-handling":3},{"id":4,"title":5,"body":6,"description":3627,"extension":3628,"meta":3629,"navigation":112,"path":3630,"seo":3631,"stem":3632,"__hash__":3633},"content\u002Fgo\u002F15-error-handling.md","15 — Error Handling",{"type":7,"value":8,"toc":3621},"minimark",[9,13,22,27,3537,3604,3608,3617],[10,11,5],"h1",{"id":12},"_15-error-handling",[14,15,16,17,21],"p",{},"Go has no exceptions. Errors are values returned from functions. This makes error handling explicit and visible — every failure point is a visible ",[18,19,20],"code",{},"if err != nil"," check.",[23,24,26],"h2",{"id":25},"error-wrapping-the-stack-trace-pattern","Error Wrapping — The Stack-Trace Pattern",[28,29,31,404,411],"code-wrapper",{"language":30},"go",[32,33,37],"pre",{"className":34,"code":35,"language":30,"meta":36,"style":36},"language-go shiki shiki-themes github-light github-dark","\u002F\u002F ┌──────────────────────────────────────────────────────────────────────┐\n\u002F\u002F │ Error chain: each layer wraps with context.                         │\n\u002F\u002F │                                                                      │\n\u002F\u002F │   db.Exec(...)        → *pq.Error: connection refused               │\n\u002F\u002F │   ↳ repo.Save()      → \"save user: connection refused\"             │\n\u002F\u002F │   ↳ handler.Create() → \"create user: save user: connection refused\"│\n\u002F\u002F │   ↳ main              → logs full chain                              │\n\u002F\u002F │                                                                      │\n\u002F\u002F │ fmt.Errorf(\"context: %w\", err) wraps — preserves err for Is\u002FAs.     │\n\u002F\u002F │ fmt.Errorf(\"context: %v\", err) formats — loses err (no Is\u002FAs match)│\n\u002F\u002F └──────────────────────────────────────────────────────────────────────┘\n\nfunc saveUser(ctx context.Context, db *sql.DB, u *User) error {\n    _, err := db.ExecContext(ctx, \"INSERT INTO users ...\", u.Name, u.Email)\n    if err != nil {\n        return fmt.Errorf(\"save user %d: %w\", u.ID, err)  \u002F\u002F wrap + add context\n    }\n    return nil\n}\n\nfunc createUser(ctx context.Context, db *sql.DB, u *User) error {\n    if err := saveUser(ctx, db, u); err != nil {\n        return fmt.Errorf(\"create user: %w\", err)  \u002F\u002F wrap again\n    }\n    return nil\n}\n\n\u002F\u002F Final error message: \"create user: save user 42: connection refused\"\n\u002F\u002F The original *pq.Error is preserved for errors.Is\u002FAs at the bottom.\n","",[18,38,39,48,54,60,66,72,78,84,89,95,101,107,114,178,203,221,256,262,271,277,282,326,346,370,375,382,387,392,398],{"__ignoreMap":36},[40,41,44],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sdCPZ","\u002F\u002F ┌──────────────────────────────────────────────────────────────────────┐\n",[40,49,51],{"class":42,"line":50},2,[40,52,53],{"class":46},"\u002F\u002F │ Error chain: each layer wraps with context.                         │\n",[40,55,57],{"class":42,"line":56},3,[40,58,59],{"class":46},"\u002F\u002F │                                                                      │\n",[40,61,63],{"class":42,"line":62},4,[40,64,65],{"class":46},"\u002F\u002F │   db.Exec(...)        → *pq.Error: connection refused               │\n",[40,67,69],{"class":42,"line":68},5,[40,70,71],{"class":46},"\u002F\u002F │   ↳ repo.Save()      → \"save user: connection refused\"             │\n",[40,73,75],{"class":42,"line":74},6,[40,76,77],{"class":46},"\u002F\u002F │   ↳ handler.Create() → \"create user: save user: connection refused\"│\n",[40,79,81],{"class":42,"line":80},7,[40,82,83],{"class":46},"\u002F\u002F │   ↳ main              → logs full chain                              │\n",[40,85,87],{"class":42,"line":86},8,[40,88,59],{"class":46},[40,90,92],{"class":42,"line":91},9,[40,93,94],{"class":46},"\u002F\u002F │ fmt.Errorf(\"context: %w\", err) wraps — preserves err for Is\u002FAs.     │\n",[40,96,98],{"class":42,"line":97},10,[40,99,100],{"class":46},"\u002F\u002F │ fmt.Errorf(\"context: %v\", err) formats — loses err (no Is\u002FAs match)│\n",[40,102,104],{"class":42,"line":103},11,[40,105,106],{"class":46},"\u002F\u002F └──────────────────────────────────────────────────────────────────────┘\n",[40,108,110],{"class":42,"line":109},12,[40,111,113],{"emptyLinePlaceholder":112},true,"\n",[40,115,117,121,125,129,133,136,139,142,145,148,151,154,156,159,161,164,166,169,172,175],{"class":42,"line":116},13,[40,118,120],{"class":119},"svdQ7","func",[40,122,124],{"class":123},"sIsaT"," saveUser",[40,126,128],{"class":127},"ssxIu","(",[40,130,132],{"class":131},"sCrzJ","ctx",[40,134,135],{"class":123}," context",[40,137,138],{"class":127},".",[40,140,141],{"class":123},"Context",[40,143,144],{"class":127},", ",[40,146,147],{"class":131},"db",[40,149,150],{"class":119}," *",[40,152,153],{"class":123},"sql",[40,155,138],{"class":127},[40,157,158],{"class":123},"DB",[40,160,144],{"class":127},[40,162,163],{"class":131},"u",[40,165,150],{"class":119},[40,167,168],{"class":123},"User",[40,170,171],{"class":127},") ",[40,173,174],{"class":119},"error",[40,176,177],{"class":127}," {\n",[40,179,181,184,187,190,193,196,200],{"class":42,"line":180},14,[40,182,183],{"class":127},"    _, err ",[40,185,186],{"class":119},":=",[40,188,189],{"class":127}," db.",[40,191,192],{"class":123},"ExecContext",[40,194,195],{"class":127},"(ctx, ",[40,197,199],{"class":198},"sJ6F3","\"INSERT INTO users ...\"",[40,201,202],{"class":127},", u.Name, u.Email)\n",[40,204,206,209,212,215,219],{"class":42,"line":205},15,[40,207,208],{"class":119},"    if",[40,210,211],{"class":127}," err ",[40,213,214],{"class":119},"!=",[40,216,218],{"class":217},"snvgF"," nil",[40,220,177],{"class":127},[40,222,224,227,230,233,235,238,241,244,247,250,253],{"class":42,"line":223},16,[40,225,226],{"class":119},"        return",[40,228,229],{"class":127}," fmt.",[40,231,232],{"class":123},"Errorf",[40,234,128],{"class":127},[40,236,237],{"class":198},"\"save user ",[40,239,240],{"class":217},"%d",[40,242,243],{"class":198},": ",[40,245,246],{"class":217},"%w",[40,248,249],{"class":198},"\"",[40,251,252],{"class":127},", u.ID, err)  ",[40,254,255],{"class":46},"\u002F\u002F wrap + add context\n",[40,257,259],{"class":42,"line":258},17,[40,260,261],{"class":127},"    }\n",[40,263,265,268],{"class":42,"line":264},18,[40,266,267],{"class":119},"    return",[40,269,270],{"class":217}," nil\n",[40,272,274],{"class":42,"line":273},19,[40,275,276],{"class":127},"}\n",[40,278,280],{"class":42,"line":279},20,[40,281,113],{"emptyLinePlaceholder":112},[40,283,285,287,290,292,294,296,298,300,302,304,306,308,310,312,314,316,318,320,322,324],{"class":42,"line":284},21,[40,286,120],{"class":119},[40,288,289],{"class":123}," createUser",[40,291,128],{"class":127},[40,293,132],{"class":131},[40,295,135],{"class":123},[40,297,138],{"class":127},[40,299,141],{"class":123},[40,301,144],{"class":127},[40,303,147],{"class":131},[40,305,150],{"class":119},[40,307,153],{"class":123},[40,309,138],{"class":127},[40,311,158],{"class":123},[40,313,144],{"class":127},[40,315,163],{"class":131},[40,317,150],{"class":119},[40,319,168],{"class":123},[40,321,171],{"class":127},[40,323,174],{"class":119},[40,325,177],{"class":127},[40,327,329,331,333,335,337,340,342,344],{"class":42,"line":328},22,[40,330,208],{"class":119},[40,332,211],{"class":127},[40,334,186],{"class":119},[40,336,124],{"class":123},[40,338,339],{"class":127},"(ctx, db, u); err ",[40,341,214],{"class":119},[40,343,218],{"class":217},[40,345,177],{"class":127},[40,347,349,351,353,355,357,360,362,364,367],{"class":42,"line":348},23,[40,350,226],{"class":119},[40,352,229],{"class":127},[40,354,232],{"class":123},[40,356,128],{"class":127},[40,358,359],{"class":198},"\"create user: ",[40,361,246],{"class":217},[40,363,249],{"class":198},[40,365,366],{"class":127},", err)  ",[40,368,369],{"class":46},"\u002F\u002F wrap again\n",[40,371,373],{"class":42,"line":372},24,[40,374,261],{"class":127},[40,376,378,380],{"class":42,"line":377},25,[40,379,267],{"class":119},[40,381,270],{"class":217},[40,383,385],{"class":42,"line":384},26,[40,386,276],{"class":127},[40,388,390],{"class":42,"line":389},27,[40,391,113],{"emptyLinePlaceholder":112},[40,393,395],{"class":42,"line":394},28,[40,396,397],{"class":46},"\u002F\u002F Final error message: \"create user: save user 42: connection refused\"\n",[40,399,401],{"class":42,"line":400},29,[40,402,403],{"class":46},"\u002F\u002F The original *pq.Error is preserved for errors.Is\u002FAs at the bottom.\n",[23,405,407,410],{"id":406},"errorsis-sentinel-error-matching",[18,408,409],{},"errors.Is"," — Sentinel Error Matching",[28,412,413,736,743],{"language":30},[32,414,416],{"className":34,"code":415,"language":30,"meta":36,"style":36},"\u002F\u002F errors.Is walks the wrap chain and checks if ANY error in the chain\n\u002F\u002F matches the target (using == or a custom Is() method).\n\nimport (\n    \"errors\"\n    \"io\"\n    \"os\"\n)\n\nfunc readFile(path string) ([]byte, error) {\n    data, err := os.ReadFile(path)\n    if err != nil {\n        return nil, fmt.Errorf(\"read %s: %w\", path, err)\n    }\n    return data, nil\n}\n\nfunc handleFile(path string) {\n    _, err := readFile(path)\n    if errors.Is(err, os.ErrNotExist) {\n        fmt.Println(\"file not found — creating default\")\n        \u002F\u002F Handle \"not found\" specifically\n    } else if errors.Is(err, io.EOF) {\n        fmt.Println(\"unexpected EOF\")\n    } else if err != nil {\n        log.Fatal(err)  \u002F\u002F other errors\n    }\n}\n\n\u002F\u002F ❌ ANTI-PATTERN: using == directly (misses wrapped errors)\n\u002F\u002F if err == os.ErrNotExist { ... }  \u002F\u002F false if err wraps os.ErrNotExist\n\u002F\u002F ✅ errors.Is walks the chain — works with wrapped errors\n",[18,417,418,423,428,432,440,451,460,469,474,478,506,522,534,562,566,576,580,584,599,609,622,637,642,660,673,689,703,707,711,715,721,730],{"__ignoreMap":36},[40,419,420],{"class":42,"line":43},[40,421,422],{"class":46},"\u002F\u002F errors.Is walks the wrap chain and checks if ANY error in the chain\n",[40,424,425],{"class":42,"line":50},[40,426,427],{"class":46},"\u002F\u002F matches the target (using == or a custom Is() method).\n",[40,429,430],{"class":42,"line":56},[40,431,113],{"emptyLinePlaceholder":112},[40,433,434,437],{"class":42,"line":62},[40,435,436],{"class":119},"import",[40,438,439],{"class":127}," (\n",[40,441,442,445,448],{"class":42,"line":68},[40,443,444],{"class":198},"    \"",[40,446,447],{"class":123},"errors",[40,449,450],{"class":198},"\"\n",[40,452,453,455,458],{"class":42,"line":74},[40,454,444],{"class":198},[40,456,457],{"class":123},"io",[40,459,450],{"class":198},[40,461,462,464,467],{"class":42,"line":80},[40,463,444],{"class":198},[40,465,466],{"class":123},"os",[40,468,450],{"class":198},[40,470,471],{"class":42,"line":86},[40,472,473],{"class":127},")\n",[40,475,476],{"class":42,"line":91},[40,477,113],{"emptyLinePlaceholder":112},[40,479,480,482,485,487,490,493,496,499,501,503],{"class":42,"line":97},[40,481,120],{"class":119},[40,483,484],{"class":123}," readFile",[40,486,128],{"class":127},[40,488,489],{"class":131},"path",[40,491,492],{"class":119}," string",[40,494,495],{"class":127},") ([]",[40,497,498],{"class":119},"byte",[40,500,144],{"class":127},[40,502,174],{"class":119},[40,504,505],{"class":127},") {\n",[40,507,508,511,513,516,519],{"class":42,"line":103},[40,509,510],{"class":127},"    data, err ",[40,512,186],{"class":119},[40,514,515],{"class":127}," os.",[40,517,518],{"class":123},"ReadFile",[40,520,521],{"class":127},"(path)\n",[40,523,524,526,528,530,532],{"class":42,"line":109},[40,525,208],{"class":119},[40,527,211],{"class":127},[40,529,214],{"class":119},[40,531,218],{"class":217},[40,533,177],{"class":127},[40,535,536,538,540,543,545,547,550,553,555,557,559],{"class":42,"line":116},[40,537,226],{"class":119},[40,539,218],{"class":217},[40,541,542],{"class":127},", fmt.",[40,544,232],{"class":123},[40,546,128],{"class":127},[40,548,549],{"class":198},"\"read ",[40,551,552],{"class":217},"%s",[40,554,243],{"class":198},[40,556,246],{"class":217},[40,558,249],{"class":198},[40,560,561],{"class":127},", path, err)\n",[40,563,564],{"class":42,"line":180},[40,565,261],{"class":127},[40,567,568,570,573],{"class":42,"line":205},[40,569,267],{"class":119},[40,571,572],{"class":127}," data, ",[40,574,575],{"class":217},"nil\n",[40,577,578],{"class":42,"line":223},[40,579,276],{"class":127},[40,581,582],{"class":42,"line":258},[40,583,113],{"emptyLinePlaceholder":112},[40,585,586,588,591,593,595,597],{"class":42,"line":264},[40,587,120],{"class":119},[40,589,590],{"class":123}," handleFile",[40,592,128],{"class":127},[40,594,489],{"class":131},[40,596,492],{"class":119},[40,598,505],{"class":127},[40,600,601,603,605,607],{"class":42,"line":273},[40,602,183],{"class":127},[40,604,186],{"class":119},[40,606,484],{"class":123},[40,608,521],{"class":127},[40,610,611,613,616,619],{"class":42,"line":279},[40,612,208],{"class":119},[40,614,615],{"class":127}," errors.",[40,617,618],{"class":123},"Is",[40,620,621],{"class":127},"(err, os.ErrNotExist) {\n",[40,623,624,627,630,632,635],{"class":42,"line":284},[40,625,626],{"class":127},"        fmt.",[40,628,629],{"class":123},"Println",[40,631,128],{"class":127},[40,633,634],{"class":198},"\"file not found — creating default\"",[40,636,473],{"class":127},[40,638,639],{"class":42,"line":328},[40,640,641],{"class":46},"        \u002F\u002F Handle \"not found\" specifically\n",[40,643,644,647,650,653,655,657],{"class":42,"line":348},[40,645,646],{"class":127},"    } ",[40,648,649],{"class":119},"else",[40,651,652],{"class":119}," if",[40,654,615],{"class":127},[40,656,618],{"class":123},[40,658,659],{"class":127},"(err, io.EOF) {\n",[40,661,662,664,666,668,671],{"class":42,"line":372},[40,663,626],{"class":127},[40,665,629],{"class":123},[40,667,128],{"class":127},[40,669,670],{"class":198},"\"unexpected EOF\"",[40,672,473],{"class":127},[40,674,675,677,679,681,683,685,687],{"class":42,"line":377},[40,676,646],{"class":127},[40,678,649],{"class":119},[40,680,652],{"class":119},[40,682,211],{"class":127},[40,684,214],{"class":119},[40,686,218],{"class":217},[40,688,177],{"class":127},[40,690,691,694,697,700],{"class":42,"line":384},[40,692,693],{"class":127},"        log.",[40,695,696],{"class":123},"Fatal",[40,698,699],{"class":127},"(err)  ",[40,701,702],{"class":46},"\u002F\u002F other errors\n",[40,704,705],{"class":42,"line":389},[40,706,261],{"class":127},[40,708,709],{"class":42,"line":394},[40,710,276],{"class":127},[40,712,713],{"class":42,"line":400},[40,714,113],{"emptyLinePlaceholder":112},[40,716,718],{"class":42,"line":717},30,[40,719,720],{"class":46},"\u002F\u002F ❌ ANTI-PATTERN: using == directly (misses wrapped errors)\n",[40,722,724,727],{"class":42,"line":723},31,[40,725,726],{"class":46},"\u002F\u002F if err == os.ErrNotExist { ... }",[40,728,729],{"class":46},"  \u002F\u002F false if err wraps os.ErrNotExist\n",[40,731,733],{"class":42,"line":732},32,[40,734,735],{"class":46},"\u002F\u002F ✅ errors.Is walks the chain — works with wrapped errors\n",[23,737,739,742],{"id":738},"errorsas-typed-error-extraction",[18,740,741],{},"errors.As"," — Typed Error Extraction",[28,744,745,1368,1372],{"language":30},[32,746,748],{"className":34,"code":747,"language":30,"meta":36,"style":36},"\u002F\u002F errors.As walks the chain and, if it finds an error of the target type,\n\u002F\u002F assigns it to the target and returns true.\n\u002F\u002F Lets callers extract structured error fields.\n\nfunc handleDBError(err error) {\n    var pqErr *pq.Error\n    if errors.As(err, &pqErr) {  \u002F\u002F ⚠️ &pqErr — pointer to the target\n        switch pqErr.Code {\n        case \"23505\":  \u002F\u002F unique_violation\n            fmt.Println(\"duplicate key:\", pqErr.Detail)\n        case \"23503\":  \u002F\u002F foreign_key_violation\n            fmt.Println(\"foreign key violation:\", pqErr.Detail)\n        default:\n            fmt.Println(\"postgres error:\", pqErr.Code, pqErr.Message)\n        }\n    }\n}\n\n\u002F\u002F ─── Custom error type with structured fields ───\ntype ValidationError struct {\n    Field   string\n    Message string\n    Code    int\n}\n\nfunc (e *ValidationError) Error() string {\n    return fmt.Sprintf(\"validation [%s]: %s (code %d)\", e.Field, e.Message, e.Code)\n}\n\nfunc validate(u *User) error {\n    if u.Email == \"\" {\n        return &ValidationError{Field: \"email\", Message: \"required\", Code: 1001}\n    }\n    if !strings.Contains(u.Email, \"@\") {\n        return &ValidationError{Field: \"email\", Message: \"invalid format\", Code: 1002}\n    }\n    return nil\n}\n\n\u002F\u002F Caller extracts the structured error:\nfunc createUser(u *User) {\n    if err := validate(u); err != nil {\n        var ve *ValidationError\n        if errors.As(err, &ve) {\n            \u002F\u002F ve.Field = \"email\", ve.Code = 1001 — structured error handling\n            http.Error(w, fmt.Sprintf(`{\"error\":\"%s\",\"field\":\"%s\"}`, ve.Message, ve.Field), 400)\n            return\n        }\n        \u002F\u002F Unknown error type — log and return 500\n        log.Printf(\"unexpected error: %v\", err)\n        http.Error(w, \"internal error\", 500)\n    }\n}\n",[18,749,750,755,760,765,769,786,805,826,834,848,863,875,888,896,910,915,919,923,927,932,945,953,960,968,972,976,1004,1036,1040,1044,1065,1080,1109,1114,1136,1161,1166,1173,1178,1183,1189,1206,1226,1240,1257,1263,1299,1305,1310,1316,1337,1358,1363],{"__ignoreMap":36},[40,751,752],{"class":42,"line":43},[40,753,754],{"class":46},"\u002F\u002F errors.As walks the chain and, if it finds an error of the target type,\n",[40,756,757],{"class":42,"line":50},[40,758,759],{"class":46},"\u002F\u002F assigns it to the target and returns true.\n",[40,761,762],{"class":42,"line":56},[40,763,764],{"class":46},"\u002F\u002F Lets callers extract structured error fields.\n",[40,766,767],{"class":42,"line":62},[40,768,113],{"emptyLinePlaceholder":112},[40,770,771,773,776,778,781,784],{"class":42,"line":68},[40,772,120],{"class":119},[40,774,775],{"class":123}," handleDBError",[40,777,128],{"class":127},[40,779,780],{"class":131},"err",[40,782,783],{"class":119}," error",[40,785,505],{"class":127},[40,787,788,791,794,797,800,802],{"class":42,"line":74},[40,789,790],{"class":119},"    var",[40,792,793],{"class":127}," pqErr ",[40,795,796],{"class":119},"*",[40,798,799],{"class":123},"pq",[40,801,138],{"class":127},[40,803,804],{"class":123},"Error\n",[40,806,807,809,811,814,817,820,823],{"class":42,"line":80},[40,808,208],{"class":119},[40,810,615],{"class":127},[40,812,813],{"class":123},"As",[40,815,816],{"class":127},"(err, ",[40,818,819],{"class":119},"&",[40,821,822],{"class":127},"pqErr) {  ",[40,824,825],{"class":46},"\u002F\u002F ⚠️ &pqErr — pointer to the target\n",[40,827,828,831],{"class":42,"line":86},[40,829,830],{"class":119},"        switch",[40,832,833],{"class":127}," pqErr.Code {\n",[40,835,836,839,842,845],{"class":42,"line":91},[40,837,838],{"class":119},"        case",[40,840,841],{"class":198}," \"23505\"",[40,843,844],{"class":127},":  ",[40,846,847],{"class":46},"\u002F\u002F unique_violation\n",[40,849,850,853,855,857,860],{"class":42,"line":97},[40,851,852],{"class":127},"            fmt.",[40,854,629],{"class":123},[40,856,128],{"class":127},[40,858,859],{"class":198},"\"duplicate key:\"",[40,861,862],{"class":127},", pqErr.Detail)\n",[40,864,865,867,870,872],{"class":42,"line":103},[40,866,838],{"class":119},[40,868,869],{"class":198}," \"23503\"",[40,871,844],{"class":127},[40,873,874],{"class":46},"\u002F\u002F foreign_key_violation\n",[40,876,877,879,881,883,886],{"class":42,"line":109},[40,878,852],{"class":127},[40,880,629],{"class":123},[40,882,128],{"class":127},[40,884,885],{"class":198},"\"foreign key violation:\"",[40,887,862],{"class":127},[40,889,890,893],{"class":42,"line":116},[40,891,892],{"class":119},"        default",[40,894,895],{"class":127},":\n",[40,897,898,900,902,904,907],{"class":42,"line":180},[40,899,852],{"class":127},[40,901,629],{"class":123},[40,903,128],{"class":127},[40,905,906],{"class":198},"\"postgres error:\"",[40,908,909],{"class":127},", pqErr.Code, pqErr.Message)\n",[40,911,912],{"class":42,"line":205},[40,913,914],{"class":127},"        }\n",[40,916,917],{"class":42,"line":223},[40,918,261],{"class":127},[40,920,921],{"class":42,"line":258},[40,922,276],{"class":127},[40,924,925],{"class":42,"line":264},[40,926,113],{"emptyLinePlaceholder":112},[40,928,929],{"class":42,"line":273},[40,930,931],{"class":46},"\u002F\u002F ─── Custom error type with structured fields ───\n",[40,933,934,937,940,943],{"class":42,"line":279},[40,935,936],{"class":119},"type",[40,938,939],{"class":123}," ValidationError",[40,941,942],{"class":119}," struct",[40,944,177],{"class":127},[40,946,947,950],{"class":42,"line":284},[40,948,949],{"class":127},"    Field   ",[40,951,952],{"class":119},"string\n",[40,954,955,958],{"class":42,"line":328},[40,956,957],{"class":127},"    Message ",[40,959,952],{"class":119},[40,961,962,965],{"class":42,"line":348},[40,963,964],{"class":127},"    Code    ",[40,966,967],{"class":119},"int\n",[40,969,970],{"class":42,"line":372},[40,971,276],{"class":127},[40,973,974],{"class":42,"line":377},[40,975,113],{"emptyLinePlaceholder":112},[40,977,978,980,983,986,988,991,993,996,999,1002],{"class":42,"line":384},[40,979,120],{"class":119},[40,981,982],{"class":127}," (",[40,984,985],{"class":131},"e ",[40,987,796],{"class":119},[40,989,990],{"class":123},"ValidationError",[40,992,171],{"class":127},[40,994,995],{"class":123},"Error",[40,997,998],{"class":127},"() ",[40,1000,1001],{"class":119},"string",[40,1003,177],{"class":127},[40,1005,1006,1008,1010,1013,1015,1018,1020,1023,1025,1028,1030,1033],{"class":42,"line":389},[40,1007,267],{"class":119},[40,1009,229],{"class":127},[40,1011,1012],{"class":123},"Sprintf",[40,1014,128],{"class":127},[40,1016,1017],{"class":198},"\"validation [",[40,1019,552],{"class":217},[40,1021,1022],{"class":198},"]: ",[40,1024,552],{"class":217},[40,1026,1027],{"class":198}," (code ",[40,1029,240],{"class":217},[40,1031,1032],{"class":198},")\"",[40,1034,1035],{"class":127},", e.Field, e.Message, e.Code)\n",[40,1037,1038],{"class":42,"line":394},[40,1039,276],{"class":127},[40,1041,1042],{"class":42,"line":400},[40,1043,113],{"emptyLinePlaceholder":112},[40,1045,1046,1048,1051,1053,1055,1057,1059,1061,1063],{"class":42,"line":717},[40,1047,120],{"class":119},[40,1049,1050],{"class":123}," validate",[40,1052,128],{"class":127},[40,1054,163],{"class":131},[40,1056,150],{"class":119},[40,1058,168],{"class":123},[40,1060,171],{"class":127},[40,1062,174],{"class":119},[40,1064,177],{"class":127},[40,1066,1067,1069,1072,1075,1078],{"class":42,"line":723},[40,1068,208],{"class":119},[40,1070,1071],{"class":127}," u.Email ",[40,1073,1074],{"class":119},"==",[40,1076,1077],{"class":198}," \"\"",[40,1079,177],{"class":127},[40,1081,1082,1084,1087,1089,1092,1095,1098,1101,1104,1107],{"class":42,"line":732},[40,1083,226],{"class":119},[40,1085,1086],{"class":119}," &",[40,1088,990],{"class":123},[40,1090,1091],{"class":127},"{Field: ",[40,1093,1094],{"class":198},"\"email\"",[40,1096,1097],{"class":127},", Message: ",[40,1099,1100],{"class":198},"\"required\"",[40,1102,1103],{"class":127},", Code: ",[40,1105,1106],{"class":217},"1001",[40,1108,276],{"class":127},[40,1110,1112],{"class":42,"line":1111},33,[40,1113,261],{"class":127},[40,1115,1117,1119,1122,1125,1128,1131,1134],{"class":42,"line":1116},34,[40,1118,208],{"class":119},[40,1120,1121],{"class":119}," !",[40,1123,1124],{"class":127},"strings.",[40,1126,1127],{"class":123},"Contains",[40,1129,1130],{"class":127},"(u.Email, ",[40,1132,1133],{"class":198},"\"@\"",[40,1135,505],{"class":127},[40,1137,1139,1141,1143,1145,1147,1149,1151,1154,1156,1159],{"class":42,"line":1138},35,[40,1140,226],{"class":119},[40,1142,1086],{"class":119},[40,1144,990],{"class":123},[40,1146,1091],{"class":127},[40,1148,1094],{"class":198},[40,1150,1097],{"class":127},[40,1152,1153],{"class":198},"\"invalid format\"",[40,1155,1103],{"class":127},[40,1157,1158],{"class":217},"1002",[40,1160,276],{"class":127},[40,1162,1164],{"class":42,"line":1163},36,[40,1165,261],{"class":127},[40,1167,1169,1171],{"class":42,"line":1168},37,[40,1170,267],{"class":119},[40,1172,270],{"class":217},[40,1174,1176],{"class":42,"line":1175},38,[40,1177,276],{"class":127},[40,1179,1181],{"class":42,"line":1180},39,[40,1182,113],{"emptyLinePlaceholder":112},[40,1184,1186],{"class":42,"line":1185},40,[40,1187,1188],{"class":46},"\u002F\u002F Caller extracts the structured error:\n",[40,1190,1192,1194,1196,1198,1200,1202,1204],{"class":42,"line":1191},41,[40,1193,120],{"class":119},[40,1195,289],{"class":123},[40,1197,128],{"class":127},[40,1199,163],{"class":131},[40,1201,150],{"class":119},[40,1203,168],{"class":123},[40,1205,505],{"class":127},[40,1207,1209,1211,1213,1215,1217,1220,1222,1224],{"class":42,"line":1208},42,[40,1210,208],{"class":119},[40,1212,211],{"class":127},[40,1214,186],{"class":119},[40,1216,1050],{"class":123},[40,1218,1219],{"class":127},"(u); err ",[40,1221,214],{"class":119},[40,1223,218],{"class":217},[40,1225,177],{"class":127},[40,1227,1229,1232,1235,1237],{"class":42,"line":1228},43,[40,1230,1231],{"class":119},"        var",[40,1233,1234],{"class":127}," ve ",[40,1236,796],{"class":119},[40,1238,1239],{"class":123},"ValidationError\n",[40,1241,1243,1246,1248,1250,1252,1254],{"class":42,"line":1242},44,[40,1244,1245],{"class":119},"        if",[40,1247,615],{"class":127},[40,1249,813],{"class":123},[40,1251,816],{"class":127},[40,1253,819],{"class":119},[40,1255,1256],{"class":127},"ve) {\n",[40,1258,1260],{"class":42,"line":1259},45,[40,1261,1262],{"class":46},"            \u002F\u002F ve.Field = \"email\", ve.Code = 1001 — structured error handling\n",[40,1264,1266,1269,1271,1274,1276,1278,1281,1283,1286,1288,1291,1294,1297],{"class":42,"line":1265},46,[40,1267,1268],{"class":127},"            http.",[40,1270,995],{"class":123},[40,1272,1273],{"class":127},"(w, fmt.",[40,1275,1012],{"class":123},[40,1277,128],{"class":127},[40,1279,1280],{"class":198},"`{\"error\":\"",[40,1282,552],{"class":217},[40,1284,1285],{"class":198},"\",\"field\":\"",[40,1287,552],{"class":217},[40,1289,1290],{"class":198},"\"}`",[40,1292,1293],{"class":127},", ve.Message, ve.Field), ",[40,1295,1296],{"class":217},"400",[40,1298,473],{"class":127},[40,1300,1302],{"class":42,"line":1301},47,[40,1303,1304],{"class":119},"            return\n",[40,1306,1308],{"class":42,"line":1307},48,[40,1309,914],{"class":127},[40,1311,1313],{"class":42,"line":1312},49,[40,1314,1315],{"class":46},"        \u002F\u002F Unknown error type — log and return 500\n",[40,1317,1319,1321,1324,1326,1329,1332,1334],{"class":42,"line":1318},50,[40,1320,693],{"class":127},[40,1322,1323],{"class":123},"Printf",[40,1325,128],{"class":127},[40,1327,1328],{"class":198},"\"unexpected error: ",[40,1330,1331],{"class":217},"%v",[40,1333,249],{"class":198},[40,1335,1336],{"class":127},", err)\n",[40,1338,1340,1343,1345,1348,1351,1353,1356],{"class":42,"line":1339},51,[40,1341,1342],{"class":127},"        http.",[40,1344,995],{"class":123},[40,1346,1347],{"class":127},"(w, ",[40,1349,1350],{"class":198},"\"internal error\"",[40,1352,144],{"class":127},[40,1354,1355],{"class":217},"500",[40,1357,473],{"class":127},[40,1359,1361],{"class":42,"line":1360},52,[40,1362,261],{"class":127},[40,1364,1366],{"class":42,"line":1365},53,[40,1367,276],{"class":127},[23,1369,1371],{"id":1370},"sentinel-errors-package-level-error-values","Sentinel Errors — Package-Level Error Values",[28,1373,1374,1788,1792],{"language":30},[32,1375,1377],{"className":34,"code":1376,"language":30,"meta":36,"style":36},"\u002F\u002F Sentinel errors are predeclared error values callers match with errors.Is.\n\u002F\u002F Convention: name them ErrXxx (exported, package-level).\n\npackage userstore\n\nimport \"errors\"\n\nvar (\n    ErrNotFound      = errors.New(\"user: not found\")\n    ErrAlreadyExists = errors.New(\"user: already exists\")\n    ErrInvalidID     = errors.New(\"user: invalid id\")\n)\n\nfunc GetByID(ctx context.Context, id int64) (*User, error) {\n    if id \u003C= 0 {\n        return nil, fmt.Errorf(\"get by id %d: %w\", id, ErrInvalidID)\n    }\n    \u002F\u002F ... DB query ...\n    if notFound {\n        return nil, ErrNotFound  \u002F\u002F return the sentinel directly\n    }\n    return u, nil\n}\n\n\u002F\u002F Caller:\nfunc handler(ctx context.Context, store *UserStore, id int64) {\n    u, err := store.GetByID(ctx, id)\n    if errors.Is(err, userstore.ErrNotFound) {\n        http.Error(w, \"user not found\", 404)\n        return\n    }\n    if errors.Is(err, userstore.ErrInvalidID) {\n        http.Error(w, \"invalid id\", 400)\n        return\n    }\n    if err != nil {\n        log.Fatal(err)\n    }\n    _ = u\n}\n",[18,1378,1379,1384,1389,1393,1401,1405,1416,1420,1427,1447,1465,1483,1487,1491,1529,1544,1570,1574,1579,1586,1598,1602,1611,1615,1619,1624,1659,1675,1686,1704,1709,1713,1724,1741,1745,1749,1761,1770,1774,1784],{"__ignoreMap":36},[40,1380,1381],{"class":42,"line":43},[40,1382,1383],{"class":46},"\u002F\u002F Sentinel errors are predeclared error values callers match with errors.Is.\n",[40,1385,1386],{"class":42,"line":50},[40,1387,1388],{"class":46},"\u002F\u002F Convention: name them ErrXxx (exported, package-level).\n",[40,1390,1391],{"class":42,"line":56},[40,1392,113],{"emptyLinePlaceholder":112},[40,1394,1395,1398],{"class":42,"line":62},[40,1396,1397],{"class":119},"package",[40,1399,1400],{"class":123}," userstore\n",[40,1402,1403],{"class":42,"line":68},[40,1404,113],{"emptyLinePlaceholder":112},[40,1406,1407,1409,1412,1414],{"class":42,"line":74},[40,1408,436],{"class":119},[40,1410,1411],{"class":198}," \"",[40,1413,447],{"class":123},[40,1415,450],{"class":198},[40,1417,1418],{"class":42,"line":80},[40,1419,113],{"emptyLinePlaceholder":112},[40,1421,1422,1425],{"class":42,"line":86},[40,1423,1424],{"class":119},"var",[40,1426,439],{"class":127},[40,1428,1429,1432,1435,1437,1440,1442,1445],{"class":42,"line":91},[40,1430,1431],{"class":127},"    ErrNotFound      ",[40,1433,1434],{"class":119},"=",[40,1436,615],{"class":127},[40,1438,1439],{"class":123},"New",[40,1441,128],{"class":127},[40,1443,1444],{"class":198},"\"user: not found\"",[40,1446,473],{"class":127},[40,1448,1449,1452,1454,1456,1458,1460,1463],{"class":42,"line":97},[40,1450,1451],{"class":127},"    ErrAlreadyExists ",[40,1453,1434],{"class":119},[40,1455,615],{"class":127},[40,1457,1439],{"class":123},[40,1459,128],{"class":127},[40,1461,1462],{"class":198},"\"user: already exists\"",[40,1464,473],{"class":127},[40,1466,1467,1470,1472,1474,1476,1478,1481],{"class":42,"line":103},[40,1468,1469],{"class":127},"    ErrInvalidID     ",[40,1471,1434],{"class":119},[40,1473,615],{"class":127},[40,1475,1439],{"class":123},[40,1477,128],{"class":127},[40,1479,1480],{"class":198},"\"user: invalid id\"",[40,1482,473],{"class":127},[40,1484,1485],{"class":42,"line":109},[40,1486,473],{"class":127},[40,1488,1489],{"class":42,"line":116},[40,1490,113],{"emptyLinePlaceholder":112},[40,1492,1493,1495,1498,1500,1502,1504,1506,1508,1510,1513,1516,1519,1521,1523,1525,1527],{"class":42,"line":180},[40,1494,120],{"class":119},[40,1496,1497],{"class":123}," GetByID",[40,1499,128],{"class":127},[40,1501,132],{"class":131},[40,1503,135],{"class":123},[40,1505,138],{"class":127},[40,1507,141],{"class":123},[40,1509,144],{"class":127},[40,1511,1512],{"class":131},"id",[40,1514,1515],{"class":119}," int64",[40,1517,1518],{"class":127},") (",[40,1520,796],{"class":119},[40,1522,168],{"class":123},[40,1524,144],{"class":127},[40,1526,174],{"class":119},[40,1528,505],{"class":127},[40,1530,1531,1533,1536,1539,1542],{"class":42,"line":205},[40,1532,208],{"class":119},[40,1534,1535],{"class":127}," id ",[40,1537,1538],{"class":119},"\u003C=",[40,1540,1541],{"class":217}," 0",[40,1543,177],{"class":127},[40,1545,1546,1548,1550,1552,1554,1556,1559,1561,1563,1565,1567],{"class":42,"line":223},[40,1547,226],{"class":119},[40,1549,218],{"class":217},[40,1551,542],{"class":127},[40,1553,232],{"class":123},[40,1555,128],{"class":127},[40,1557,1558],{"class":198},"\"get by id ",[40,1560,240],{"class":217},[40,1562,243],{"class":198},[40,1564,246],{"class":217},[40,1566,249],{"class":198},[40,1568,1569],{"class":127},", id, ErrInvalidID)\n",[40,1571,1572],{"class":42,"line":258},[40,1573,261],{"class":127},[40,1575,1576],{"class":42,"line":264},[40,1577,1578],{"class":46},"    \u002F\u002F ... DB query ...\n",[40,1580,1581,1583],{"class":42,"line":273},[40,1582,208],{"class":119},[40,1584,1585],{"class":127}," notFound {\n",[40,1587,1588,1590,1592,1595],{"class":42,"line":279},[40,1589,226],{"class":119},[40,1591,218],{"class":217},[40,1593,1594],{"class":127},", ErrNotFound  ",[40,1596,1597],{"class":46},"\u002F\u002F return the sentinel directly\n",[40,1599,1600],{"class":42,"line":284},[40,1601,261],{"class":127},[40,1603,1604,1606,1609],{"class":42,"line":328},[40,1605,267],{"class":119},[40,1607,1608],{"class":127}," u, ",[40,1610,575],{"class":217},[40,1612,1613],{"class":42,"line":348},[40,1614,276],{"class":127},[40,1616,1617],{"class":42,"line":372},[40,1618,113],{"emptyLinePlaceholder":112},[40,1620,1621],{"class":42,"line":377},[40,1622,1623],{"class":46},"\u002F\u002F Caller:\n",[40,1625,1626,1628,1631,1633,1635,1637,1639,1641,1643,1646,1648,1651,1653,1655,1657],{"class":42,"line":384},[40,1627,120],{"class":119},[40,1629,1630],{"class":123}," handler",[40,1632,128],{"class":127},[40,1634,132],{"class":131},[40,1636,135],{"class":123},[40,1638,138],{"class":127},[40,1640,141],{"class":123},[40,1642,144],{"class":127},[40,1644,1645],{"class":131},"store",[40,1647,150],{"class":119},[40,1649,1650],{"class":123},"UserStore",[40,1652,144],{"class":127},[40,1654,1512],{"class":131},[40,1656,1515],{"class":119},[40,1658,505],{"class":127},[40,1660,1661,1664,1666,1669,1672],{"class":42,"line":389},[40,1662,1663],{"class":127},"    u, err ",[40,1665,186],{"class":119},[40,1667,1668],{"class":127}," store.",[40,1670,1671],{"class":123},"GetByID",[40,1673,1674],{"class":127},"(ctx, id)\n",[40,1676,1677,1679,1681,1683],{"class":42,"line":394},[40,1678,208],{"class":119},[40,1680,615],{"class":127},[40,1682,618],{"class":123},[40,1684,1685],{"class":127},"(err, userstore.ErrNotFound) {\n",[40,1687,1688,1690,1692,1694,1697,1699,1702],{"class":42,"line":400},[40,1689,1342],{"class":127},[40,1691,995],{"class":123},[40,1693,1347],{"class":127},[40,1695,1696],{"class":198},"\"user not found\"",[40,1698,144],{"class":127},[40,1700,1701],{"class":217},"404",[40,1703,473],{"class":127},[40,1705,1706],{"class":42,"line":717},[40,1707,1708],{"class":119},"        return\n",[40,1710,1711],{"class":42,"line":723},[40,1712,261],{"class":127},[40,1714,1715,1717,1719,1721],{"class":42,"line":732},[40,1716,208],{"class":119},[40,1718,615],{"class":127},[40,1720,618],{"class":123},[40,1722,1723],{"class":127},"(err, userstore.ErrInvalidID) {\n",[40,1725,1726,1728,1730,1732,1735,1737,1739],{"class":42,"line":1111},[40,1727,1342],{"class":127},[40,1729,995],{"class":123},[40,1731,1347],{"class":127},[40,1733,1734],{"class":198},"\"invalid id\"",[40,1736,144],{"class":127},[40,1738,1296],{"class":217},[40,1740,473],{"class":127},[40,1742,1743],{"class":42,"line":1116},[40,1744,1708],{"class":119},[40,1746,1747],{"class":42,"line":1138},[40,1748,261],{"class":127},[40,1750,1751,1753,1755,1757,1759],{"class":42,"line":1163},[40,1752,208],{"class":119},[40,1754,211],{"class":127},[40,1756,214],{"class":119},[40,1758,218],{"class":217},[40,1760,177],{"class":127},[40,1762,1763,1765,1767],{"class":42,"line":1168},[40,1764,693],{"class":127},[40,1766,696],{"class":123},[40,1768,1769],{"class":127},"(err)\n",[40,1771,1772],{"class":42,"line":1175},[40,1773,261],{"class":127},[40,1775,1776,1779,1781],{"class":42,"line":1180},[40,1777,1778],{"class":127},"    _ ",[40,1780,1434],{"class":119},[40,1782,1783],{"class":127}," u\n",[40,1785,1786],{"class":42,"line":1185},[40,1787,276],{"class":127},[23,1789,1791],{"id":1790},"the-nil-interface-trap-on-errors","The Nil-Interface Trap on Errors",[28,1793,1794,2098,2109],{"language":30},[32,1795,1797],{"className":34,"code":1796,"language":30,"meta":36,"style":36},"\u002F\u002F ❌ ANTI-PATTERN: returning a nil pointer of a concrete error type\ntype MyError struct{ Code int }\nfunc (e *MyError) Error() string { return fmt.Sprintf(\"code %d\", e.Code) }\n\nfunc badFunc(fail bool) error {\n    if fail {\n        var err *MyError = nil  \u002F\u002F nil pointer\n        return err  \u002F\u002F ❌ returns a NON-NIL error interface wrapping a nil pointer!\n    }\n    return nil\n}\n\nfunc caller() {\n    err := badFunc(true)\n    fmt.Println(err == nil)  \u002F\u002F false! — the trap\n    \u002F\u002F err.Error() would panic (nil pointer dereference)\n}\n\n\u002F\u002F ✅ CORRECT: return nil directly for a nil error\nfunc goodFunc(fail bool) error {\n    if fail {\n        return &MyError{Code: 42}  \u002F\u002F return a real error, not a nil pointer\n    }\n    return nil  \u002F\u002F return nil directly — true nil interface\n}\n\n\u002F\u002F This is the #1 error-handling bug in Go. The rule:\n\u002F\u002F   If you return an error interface, return nil directly (not a nil pointer\n\u002F\u002F   of a concrete error type). A nil pointer wrapped in an error interface\n\u002F\u002F   is non-nil — the caller's err != nil check passes, but calling err.Error()\n\u002F\u002F   panics.\n",[18,1798,1799,1804,1822,1865,1869,1890,1897,1915,1925,1929,1935,1939,1943,1953,1969,1989,1994,1998,2002,2007,2026,2032,2052,2056,2065,2069,2073,2078,2083,2088,2093],{"__ignoreMap":36},[40,1800,1801],{"class":42,"line":43},[40,1802,1803],{"class":46},"\u002F\u002F ❌ ANTI-PATTERN: returning a nil pointer of a concrete error type\n",[40,1805,1806,1808,1811,1813,1816,1819],{"class":42,"line":50},[40,1807,936],{"class":119},[40,1809,1810],{"class":123}," MyError",[40,1812,942],{"class":119},[40,1814,1815],{"class":127},"{ Code ",[40,1817,1818],{"class":119},"int",[40,1820,1821],{"class":127}," }\n",[40,1823,1824,1826,1828,1830,1832,1835,1837,1839,1841,1843,1846,1849,1851,1853,1855,1858,1860,1862],{"class":42,"line":56},[40,1825,120],{"class":119},[40,1827,982],{"class":127},[40,1829,985],{"class":131},[40,1831,796],{"class":119},[40,1833,1834],{"class":123},"MyError",[40,1836,171],{"class":127},[40,1838,995],{"class":123},[40,1840,998],{"class":127},[40,1842,1001],{"class":119},[40,1844,1845],{"class":127}," { ",[40,1847,1848],{"class":119},"return",[40,1850,229],{"class":127},[40,1852,1012],{"class":123},[40,1854,128],{"class":127},[40,1856,1857],{"class":198},"\"code ",[40,1859,240],{"class":217},[40,1861,249],{"class":198},[40,1863,1864],{"class":127},", e.Code) }\n",[40,1866,1867],{"class":42,"line":62},[40,1868,113],{"emptyLinePlaceholder":112},[40,1870,1871,1873,1876,1878,1881,1884,1886,1888],{"class":42,"line":68},[40,1872,120],{"class":119},[40,1874,1875],{"class":123}," badFunc",[40,1877,128],{"class":127},[40,1879,1880],{"class":131},"fail",[40,1882,1883],{"class":119}," bool",[40,1885,171],{"class":127},[40,1887,174],{"class":119},[40,1889,177],{"class":127},[40,1891,1892,1894],{"class":42,"line":74},[40,1893,208],{"class":119},[40,1895,1896],{"class":127}," fail {\n",[40,1898,1899,1901,1903,1905,1907,1910,1912],{"class":42,"line":80},[40,1900,1231],{"class":119},[40,1902,211],{"class":127},[40,1904,796],{"class":119},[40,1906,1834],{"class":123},[40,1908,1909],{"class":119}," =",[40,1911,218],{"class":217},[40,1913,1914],{"class":46},"  \u002F\u002F nil pointer\n",[40,1916,1917,1919,1922],{"class":42,"line":86},[40,1918,226],{"class":119},[40,1920,1921],{"class":127}," err  ",[40,1923,1924],{"class":46},"\u002F\u002F ❌ returns a NON-NIL error interface wrapping a nil pointer!\n",[40,1926,1927],{"class":42,"line":91},[40,1928,261],{"class":127},[40,1930,1931,1933],{"class":42,"line":97},[40,1932,267],{"class":119},[40,1934,270],{"class":217},[40,1936,1937],{"class":42,"line":103},[40,1938,276],{"class":127},[40,1940,1941],{"class":42,"line":109},[40,1942,113],{"emptyLinePlaceholder":112},[40,1944,1945,1947,1950],{"class":42,"line":116},[40,1946,120],{"class":119},[40,1948,1949],{"class":123}," caller",[40,1951,1952],{"class":127},"() {\n",[40,1954,1955,1958,1960,1962,1964,1967],{"class":42,"line":180},[40,1956,1957],{"class":127},"    err ",[40,1959,186],{"class":119},[40,1961,1875],{"class":123},[40,1963,128],{"class":127},[40,1965,1966],{"class":217},"true",[40,1968,473],{"class":127},[40,1970,1971,1974,1976,1979,1981,1983,1986],{"class":42,"line":205},[40,1972,1973],{"class":127},"    fmt.",[40,1975,629],{"class":123},[40,1977,1978],{"class":127},"(err ",[40,1980,1074],{"class":119},[40,1982,218],{"class":217},[40,1984,1985],{"class":127},")  ",[40,1987,1988],{"class":46},"\u002F\u002F false! — the trap\n",[40,1990,1991],{"class":42,"line":223},[40,1992,1993],{"class":46},"    \u002F\u002F err.Error() would panic (nil pointer dereference)\n",[40,1995,1996],{"class":42,"line":258},[40,1997,276],{"class":127},[40,1999,2000],{"class":42,"line":264},[40,2001,113],{"emptyLinePlaceholder":112},[40,2003,2004],{"class":42,"line":273},[40,2005,2006],{"class":46},"\u002F\u002F ✅ CORRECT: return nil directly for a nil error\n",[40,2008,2009,2011,2014,2016,2018,2020,2022,2024],{"class":42,"line":279},[40,2010,120],{"class":119},[40,2012,2013],{"class":123}," goodFunc",[40,2015,128],{"class":127},[40,2017,1880],{"class":131},[40,2019,1883],{"class":119},[40,2021,171],{"class":127},[40,2023,174],{"class":119},[40,2025,177],{"class":127},[40,2027,2028,2030],{"class":42,"line":284},[40,2029,208],{"class":119},[40,2031,1896],{"class":127},[40,2033,2034,2036,2038,2040,2043,2046,2049],{"class":42,"line":328},[40,2035,226],{"class":119},[40,2037,1086],{"class":119},[40,2039,1834],{"class":123},[40,2041,2042],{"class":127},"{Code: ",[40,2044,2045],{"class":217},"42",[40,2047,2048],{"class":127},"}  ",[40,2050,2051],{"class":46},"\u002F\u002F return a real error, not a nil pointer\n",[40,2053,2054],{"class":42,"line":348},[40,2055,261],{"class":127},[40,2057,2058,2060,2062],{"class":42,"line":372},[40,2059,267],{"class":119},[40,2061,218],{"class":217},[40,2063,2064],{"class":46},"  \u002F\u002F return nil directly — true nil interface\n",[40,2066,2067],{"class":42,"line":377},[40,2068,276],{"class":127},[40,2070,2071],{"class":42,"line":384},[40,2072,113],{"emptyLinePlaceholder":112},[40,2074,2075],{"class":42,"line":389},[40,2076,2077],{"class":46},"\u002F\u002F This is the #1 error-handling bug in Go. The rule:\n",[40,2079,2080],{"class":42,"line":394},[40,2081,2082],{"class":46},"\u002F\u002F   If you return an error interface, return nil directly (not a nil pointer\n",[40,2084,2085],{"class":42,"line":400},[40,2086,2087],{"class":46},"\u002F\u002F   of a concrete error type). A nil pointer wrapped in an error interface\n",[40,2089,2090],{"class":42,"line":717},[40,2091,2092],{"class":46},"\u002F\u002F   is non-nil — the caller's err != nil check passes, but calling err.Error()\n",[40,2094,2095],{"class":42,"line":723},[40,2096,2097],{"class":46},"\u002F\u002F   panics.\n",[23,2099,2101,2104,2105,2108],{"id":2100},"panic-and-recover-when-and-how",[18,2102,2103],{},"panic"," and ",[18,2106,2107],{},"recover"," — When and How",[28,2110,2111,2520,2524],{"language":30},[32,2112,2114],{"className":34,"code":2113,"language":30,"meta":36,"style":36},"\u002F\u002F panic is for UNRECOVERABLE conditions — bugs, invariant violations.\n\u002F\u002F NOT for normal error handling. Return an error for expected failures.\n\n\u002F\u002F ─── Legitimate panic: \"must\" functions (programmer error) ───\nfunc MustParseTemplate(src string) *template.Template {\n    t, err := template.New(\"\").Parse(src)\n    if err != nil {\n        panic(fmt.Sprintf(\"template parse error (programmer bug): %v\", err))\n    }\n    return t\n}\n\u002F\u002F \"Must\" prefix convention: the input is a compile-time constant; if it\n\u002F\u002F fails, it's a programmer mistake that should be caught during development.\n\n\u002F\u002F ─── recover — catching panics (rare, mainly in middleware) ───\nfunc safeHandler(fn func()) (err error) {\n    defer func() {\n        if r := recover(); r != nil {\n            err = fmt.Errorf(\"panic recovered: %v\", r)\n            \u002F\u002F Log the stack trace for debugging:\n            log.Printf(\"panic: %v\\n%s\", r, debug.Stack())\n        }\n    }()\n    fn()\n    return nil\n}\n\n\u002F\u002F ─── recover in goroutines (prevent program crash) ───\nfunc safeGo(fn func()) {\n    go func() {\n        defer func() {\n            if r := recover(); r != nil {\n                log.Printf(\"goroutine panic: %v\\n%s\", r, debug.Stack())\n            }\n        }()\n        fn()\n    }()\n}\n\n\u002F\u002F ⚠️ An unrecovered panic in a goroutine CRASHES THE ENTIRE PROGRAM.\n\u002F\u002F Always recover in goroutines that handle external input (HTTP handlers,\n\u002F\u002F message consumers) — a single bad request shouldn't take down the server.\n",[18,2115,2116,2121,2126,2130,2135,2163,2189,2201,2223,2227,2234,2238,2243,2248,2252,2257,2281,2290,2311,2334,2339,2365,2369,2374,2382,2388,2392,2396,2401,2417,2426,2435,2454,2476,2481,2486,2493,2497,2501,2505,2510,2515],{"__ignoreMap":36},[40,2117,2118],{"class":42,"line":43},[40,2119,2120],{"class":46},"\u002F\u002F panic is for UNRECOVERABLE conditions — bugs, invariant violations.\n",[40,2122,2123],{"class":42,"line":50},[40,2124,2125],{"class":46},"\u002F\u002F NOT for normal error handling. Return an error for expected failures.\n",[40,2127,2128],{"class":42,"line":56},[40,2129,113],{"emptyLinePlaceholder":112},[40,2131,2132],{"class":42,"line":62},[40,2133,2134],{"class":46},"\u002F\u002F ─── Legitimate panic: \"must\" functions (programmer error) ───\n",[40,2136,2137,2139,2142,2144,2147,2149,2151,2153,2156,2158,2161],{"class":42,"line":68},[40,2138,120],{"class":119},[40,2140,2141],{"class":123}," MustParseTemplate",[40,2143,128],{"class":127},[40,2145,2146],{"class":131},"src",[40,2148,492],{"class":119},[40,2150,171],{"class":127},[40,2152,796],{"class":119},[40,2154,2155],{"class":123},"template",[40,2157,138],{"class":127},[40,2159,2160],{"class":123},"Template",[40,2162,177],{"class":127},[40,2164,2165,2168,2170,2173,2175,2177,2180,2183,2186],{"class":42,"line":74},[40,2166,2167],{"class":127},"    t, err ",[40,2169,186],{"class":119},[40,2171,2172],{"class":127}," template.",[40,2174,1439],{"class":123},[40,2176,128],{"class":127},[40,2178,2179],{"class":198},"\"\"",[40,2181,2182],{"class":127},").",[40,2184,2185],{"class":123},"Parse",[40,2187,2188],{"class":127},"(src)\n",[40,2190,2191,2193,2195,2197,2199],{"class":42,"line":80},[40,2192,208],{"class":119},[40,2194,211],{"class":127},[40,2196,214],{"class":119},[40,2198,218],{"class":217},[40,2200,177],{"class":127},[40,2202,2203,2206,2209,2211,2213,2216,2218,2220],{"class":42,"line":86},[40,2204,2205],{"class":123},"        panic",[40,2207,2208],{"class":127},"(fmt.",[40,2210,1012],{"class":123},[40,2212,128],{"class":127},[40,2214,2215],{"class":198},"\"template parse error (programmer bug): ",[40,2217,1331],{"class":217},[40,2219,249],{"class":198},[40,2221,2222],{"class":127},", err))\n",[40,2224,2225],{"class":42,"line":91},[40,2226,261],{"class":127},[40,2228,2229,2231],{"class":42,"line":97},[40,2230,267],{"class":119},[40,2232,2233],{"class":127}," t\n",[40,2235,2236],{"class":42,"line":103},[40,2237,276],{"class":127},[40,2239,2240],{"class":42,"line":109},[40,2241,2242],{"class":46},"\u002F\u002F \"Must\" prefix convention: the input is a compile-time constant; if it\n",[40,2244,2245],{"class":42,"line":116},[40,2246,2247],{"class":46},"\u002F\u002F fails, it's a programmer mistake that should be caught during development.\n",[40,2249,2250],{"class":42,"line":180},[40,2251,113],{"emptyLinePlaceholder":112},[40,2253,2254],{"class":42,"line":205},[40,2255,2256],{"class":46},"\u002F\u002F ─── recover — catching panics (rare, mainly in middleware) ───\n",[40,2258,2259,2261,2264,2266,2269,2272,2275,2277,2279],{"class":42,"line":223},[40,2260,120],{"class":119},[40,2262,2263],{"class":123}," safeHandler",[40,2265,128],{"class":127},[40,2267,2268],{"class":131},"fn",[40,2270,2271],{"class":119}," func",[40,2273,2274],{"class":127},"()) (",[40,2276,780],{"class":131},[40,2278,783],{"class":119},[40,2280,505],{"class":127},[40,2282,2283,2286,2288],{"class":42,"line":258},[40,2284,2285],{"class":119},"    defer",[40,2287,2271],{"class":119},[40,2289,1952],{"class":127},[40,2291,2292,2294,2297,2299,2302,2305,2307,2309],{"class":42,"line":264},[40,2293,1245],{"class":119},[40,2295,2296],{"class":127}," r ",[40,2298,186],{"class":119},[40,2300,2301],{"class":123}," recover",[40,2303,2304],{"class":127},"(); r ",[40,2306,214],{"class":119},[40,2308,218],{"class":217},[40,2310,177],{"class":127},[40,2312,2313,2316,2318,2320,2322,2324,2327,2329,2331],{"class":42,"line":273},[40,2314,2315],{"class":127},"            err ",[40,2317,1434],{"class":119},[40,2319,229],{"class":127},[40,2321,232],{"class":123},[40,2323,128],{"class":127},[40,2325,2326],{"class":198},"\"panic recovered: ",[40,2328,1331],{"class":217},[40,2330,249],{"class":198},[40,2332,2333],{"class":127},", r)\n",[40,2335,2336],{"class":42,"line":279},[40,2337,2338],{"class":46},"            \u002F\u002F Log the stack trace for debugging:\n",[40,2340,2341,2344,2346,2348,2351,2354,2356,2359,2362],{"class":42,"line":284},[40,2342,2343],{"class":127},"            log.",[40,2345,1323],{"class":123},[40,2347,128],{"class":127},[40,2349,2350],{"class":198},"\"panic: ",[40,2352,2353],{"class":217},"%v\\n%s",[40,2355,249],{"class":198},[40,2357,2358],{"class":127},", r, debug.",[40,2360,2361],{"class":123},"Stack",[40,2363,2364],{"class":127},"())\n",[40,2366,2367],{"class":42,"line":328},[40,2368,914],{"class":127},[40,2370,2371],{"class":42,"line":348},[40,2372,2373],{"class":127},"    }()\n",[40,2375,2376,2379],{"class":42,"line":372},[40,2377,2378],{"class":123},"    fn",[40,2380,2381],{"class":127},"()\n",[40,2383,2384,2386],{"class":42,"line":377},[40,2385,267],{"class":119},[40,2387,270],{"class":217},[40,2389,2390],{"class":42,"line":384},[40,2391,276],{"class":127},[40,2393,2394],{"class":42,"line":389},[40,2395,113],{"emptyLinePlaceholder":112},[40,2397,2398],{"class":42,"line":394},[40,2399,2400],{"class":46},"\u002F\u002F ─── recover in goroutines (prevent program crash) ───\n",[40,2402,2403,2405,2408,2410,2412,2414],{"class":42,"line":400},[40,2404,120],{"class":119},[40,2406,2407],{"class":123}," safeGo",[40,2409,128],{"class":127},[40,2411,2268],{"class":131},[40,2413,2271],{"class":119},[40,2415,2416],{"class":127},"()) {\n",[40,2418,2419,2422,2424],{"class":42,"line":717},[40,2420,2421],{"class":119},"    go",[40,2423,2271],{"class":119},[40,2425,1952],{"class":127},[40,2427,2428,2431,2433],{"class":42,"line":723},[40,2429,2430],{"class":119},"        defer",[40,2432,2271],{"class":119},[40,2434,1952],{"class":127},[40,2436,2437,2440,2442,2444,2446,2448,2450,2452],{"class":42,"line":732},[40,2438,2439],{"class":119},"            if",[40,2441,2296],{"class":127},[40,2443,186],{"class":119},[40,2445,2301],{"class":123},[40,2447,2304],{"class":127},[40,2449,214],{"class":119},[40,2451,218],{"class":217},[40,2453,177],{"class":127},[40,2455,2456,2459,2461,2463,2466,2468,2470,2472,2474],{"class":42,"line":1111},[40,2457,2458],{"class":127},"                log.",[40,2460,1323],{"class":123},[40,2462,128],{"class":127},[40,2464,2465],{"class":198},"\"goroutine panic: ",[40,2467,2353],{"class":217},[40,2469,249],{"class":198},[40,2471,2358],{"class":127},[40,2473,2361],{"class":123},[40,2475,2364],{"class":127},[40,2477,2478],{"class":42,"line":1116},[40,2479,2480],{"class":127},"            }\n",[40,2482,2483],{"class":42,"line":1138},[40,2484,2485],{"class":127},"        }()\n",[40,2487,2488,2491],{"class":42,"line":1163},[40,2489,2490],{"class":123},"        fn",[40,2492,2381],{"class":127},[40,2494,2495],{"class":42,"line":1168},[40,2496,2373],{"class":127},[40,2498,2499],{"class":42,"line":1175},[40,2500,276],{"class":127},[40,2502,2503],{"class":42,"line":1180},[40,2504,113],{"emptyLinePlaceholder":112},[40,2506,2507],{"class":42,"line":1185},[40,2508,2509],{"class":46},"\u002F\u002F ⚠️ An unrecovered panic in a goroutine CRASHES THE ENTIRE PROGRAM.\n",[40,2511,2512],{"class":42,"line":1191},[40,2513,2514],{"class":46},"\u002F\u002F Always recover in goroutines that handle external input (HTTP handlers,\n",[40,2516,2517],{"class":42,"line":1208},[40,2518,2519],{"class":46},"\u002F\u002F message consumers) — a single bad request shouldn't take down the server.\n",[23,2521,2523],{"id":2522},"production-pattern-error-categorization","Production Pattern — Error Categorization",[28,2525,2526,2834,2841],{"language":30},[32,2527,2529],{"className":34,"code":2528,"language":30,"meta":36,"style":36},"\u002F\u002F Categorize errors for HTTP status mapping:\nfunc httpStatusFor(err error) int {\n    \u002F\u002F Check custom error types first (most specific):\n    var ve *ValidationError\n    if errors.As(err, &ve) {\n        return http.StatusBadRequest  \u002F\u002F 400\n    }\n\n    \u002F\u002F Then sentinels:\n    switch {\n    case errors.Is(err, ErrNotFound):\n        return http.StatusNotFound  \u002F\u002F 404\n    case errors.Is(err, ErrAlreadyExists):\n        return http.StatusConflict  \u002F\u002F 409\n    case errors.Is(err, ErrInvalidID):\n        return http.StatusBadRequest  \u002F\u002F 400\n    case errors.Is(err, context.DeadlineExceeded):\n        return http.StatusGatewayTimeout  \u002F\u002F 504\n    case errors.Is(err, context.Canceled):\n        return 499  \u002F\u002F client closed request (nginx convention)\n    }\n\n    \u002F\u002F Check for specific DB errors:\n    var pqErr *pq.Error\n    if errors.As(err, &pqErr) {\n        switch pqErr.Code {\n        case \"23505\":  \u002F\u002F unique_violation\n            return http.StatusConflict  \u002F\u002F 409\n        case \"23503\":  \u002F\u002F foreign_key_violation\n            return http.StatusBadRequest  \u002F\u002F 400\n        }\n    }\n\n    \u002F\u002F Fallback: internal error\n    return http.StatusInternalServerError  \u002F\u002F 500\n}\n",[18,2530,2531,2536,2555,2560,2570,2584,2594,2598,2602,2607,2614,2626,2636,2647,2657,2668,2676,2687,2697,2708,2718,2722,2726,2731,2745,2760,2766,2776,2785,2795,2803,2807,2811,2815,2820,2830],{"__ignoreMap":36},[40,2532,2533],{"class":42,"line":43},[40,2534,2535],{"class":46},"\u002F\u002F Categorize errors for HTTP status mapping:\n",[40,2537,2538,2540,2543,2545,2547,2549,2551,2553],{"class":42,"line":50},[40,2539,120],{"class":119},[40,2541,2542],{"class":123}," httpStatusFor",[40,2544,128],{"class":127},[40,2546,780],{"class":131},[40,2548,783],{"class":119},[40,2550,171],{"class":127},[40,2552,1818],{"class":119},[40,2554,177],{"class":127},[40,2556,2557],{"class":42,"line":56},[40,2558,2559],{"class":46},"    \u002F\u002F Check custom error types first (most specific):\n",[40,2561,2562,2564,2566,2568],{"class":42,"line":62},[40,2563,790],{"class":119},[40,2565,1234],{"class":127},[40,2567,796],{"class":119},[40,2569,1239],{"class":123},[40,2571,2572,2574,2576,2578,2580,2582],{"class":42,"line":68},[40,2573,208],{"class":119},[40,2575,615],{"class":127},[40,2577,813],{"class":123},[40,2579,816],{"class":127},[40,2581,819],{"class":119},[40,2583,1256],{"class":127},[40,2585,2586,2588,2591],{"class":42,"line":74},[40,2587,226],{"class":119},[40,2589,2590],{"class":127}," http.StatusBadRequest  ",[40,2592,2593],{"class":46},"\u002F\u002F 400\n",[40,2595,2596],{"class":42,"line":80},[40,2597,261],{"class":127},[40,2599,2600],{"class":42,"line":86},[40,2601,113],{"emptyLinePlaceholder":112},[40,2603,2604],{"class":42,"line":91},[40,2605,2606],{"class":46},"    \u002F\u002F Then sentinels:\n",[40,2608,2609,2612],{"class":42,"line":97},[40,2610,2611],{"class":119},"    switch",[40,2613,177],{"class":127},[40,2615,2616,2619,2621,2623],{"class":42,"line":103},[40,2617,2618],{"class":119},"    case",[40,2620,615],{"class":127},[40,2622,618],{"class":123},[40,2624,2625],{"class":127},"(err, ErrNotFound):\n",[40,2627,2628,2630,2633],{"class":42,"line":109},[40,2629,226],{"class":119},[40,2631,2632],{"class":127}," http.StatusNotFound  ",[40,2634,2635],{"class":46},"\u002F\u002F 404\n",[40,2637,2638,2640,2642,2644],{"class":42,"line":116},[40,2639,2618],{"class":119},[40,2641,615],{"class":127},[40,2643,618],{"class":123},[40,2645,2646],{"class":127},"(err, ErrAlreadyExists):\n",[40,2648,2649,2651,2654],{"class":42,"line":180},[40,2650,226],{"class":119},[40,2652,2653],{"class":127}," http.StatusConflict  ",[40,2655,2656],{"class":46},"\u002F\u002F 409\n",[40,2658,2659,2661,2663,2665],{"class":42,"line":205},[40,2660,2618],{"class":119},[40,2662,615],{"class":127},[40,2664,618],{"class":123},[40,2666,2667],{"class":127},"(err, ErrInvalidID):\n",[40,2669,2670,2672,2674],{"class":42,"line":223},[40,2671,226],{"class":119},[40,2673,2590],{"class":127},[40,2675,2593],{"class":46},[40,2677,2678,2680,2682,2684],{"class":42,"line":258},[40,2679,2618],{"class":119},[40,2681,615],{"class":127},[40,2683,618],{"class":123},[40,2685,2686],{"class":127},"(err, context.DeadlineExceeded):\n",[40,2688,2689,2691,2694],{"class":42,"line":264},[40,2690,226],{"class":119},[40,2692,2693],{"class":127}," http.StatusGatewayTimeout  ",[40,2695,2696],{"class":46},"\u002F\u002F 504\n",[40,2698,2699,2701,2703,2705],{"class":42,"line":273},[40,2700,2618],{"class":119},[40,2702,615],{"class":127},[40,2704,618],{"class":123},[40,2706,2707],{"class":127},"(err, context.Canceled):\n",[40,2709,2710,2712,2715],{"class":42,"line":279},[40,2711,226],{"class":119},[40,2713,2714],{"class":217}," 499",[40,2716,2717],{"class":46},"  \u002F\u002F client closed request (nginx convention)\n",[40,2719,2720],{"class":42,"line":284},[40,2721,261],{"class":127},[40,2723,2724],{"class":42,"line":328},[40,2725,113],{"emptyLinePlaceholder":112},[40,2727,2728],{"class":42,"line":348},[40,2729,2730],{"class":46},"    \u002F\u002F Check for specific DB errors:\n",[40,2732,2733,2735,2737,2739,2741,2743],{"class":42,"line":372},[40,2734,790],{"class":119},[40,2736,793],{"class":127},[40,2738,796],{"class":119},[40,2740,799],{"class":123},[40,2742,138],{"class":127},[40,2744,804],{"class":123},[40,2746,2747,2749,2751,2753,2755,2757],{"class":42,"line":377},[40,2748,208],{"class":119},[40,2750,615],{"class":127},[40,2752,813],{"class":123},[40,2754,816],{"class":127},[40,2756,819],{"class":119},[40,2758,2759],{"class":127},"pqErr) {\n",[40,2761,2762,2764],{"class":42,"line":384},[40,2763,830],{"class":119},[40,2765,833],{"class":127},[40,2767,2768,2770,2772,2774],{"class":42,"line":389},[40,2769,838],{"class":119},[40,2771,841],{"class":198},[40,2773,844],{"class":127},[40,2775,847],{"class":46},[40,2777,2778,2781,2783],{"class":42,"line":394},[40,2779,2780],{"class":119},"            return",[40,2782,2653],{"class":127},[40,2784,2656],{"class":46},[40,2786,2787,2789,2791,2793],{"class":42,"line":400},[40,2788,838],{"class":119},[40,2790,869],{"class":198},[40,2792,844],{"class":127},[40,2794,874],{"class":46},[40,2796,2797,2799,2801],{"class":42,"line":717},[40,2798,2780],{"class":119},[40,2800,2590],{"class":127},[40,2802,2593],{"class":46},[40,2804,2805],{"class":42,"line":723},[40,2806,914],{"class":127},[40,2808,2809],{"class":42,"line":732},[40,2810,261],{"class":127},[40,2812,2813],{"class":42,"line":1111},[40,2814,113],{"emptyLinePlaceholder":112},[40,2816,2817],{"class":42,"line":1116},[40,2818,2819],{"class":46},"    \u002F\u002F Fallback: internal error\n",[40,2821,2822,2824,2827],{"class":42,"line":1138},[40,2823,267],{"class":119},[40,2825,2826],{"class":127}," http.StatusInternalServerError  ",[40,2828,2829],{"class":46},"\u002F\u002F 500\n",[40,2831,2832],{"class":42,"line":1163},[40,2833,276],{"class":127},[23,2835,2837,2840],{"id":2836},"errorsjoin-go-120",[18,2838,2839],{},"errors.Join"," (Go 1.20+)",[28,2842,2843,3075,3079,3179,3183,3355,3359],{"language":30},[32,2844,2846],{"className":34,"code":2845,"language":30,"meta":36,"style":36},"\u002F\u002F errors.Join combines multiple errors into one.\n\u002F\u002F errors.Is\u002FAs walk all joined errors.\n\nfunc validateAll(u *User) error {\n    var errs []error\n    if u.Name == \"\" {\n        errs = append(errs, &ValidationError{Field: \"name\", Message: \"required\"})\n    }\n    if u.Email == \"\" {\n        errs = append(errs, &ValidationError{Field: \"email\", Message: \"required\"})\n    }\n    if u.Age \u003C 0 {\n        errs = append(errs, &ValidationError{Field: \"age\", Message: \"must be non-negative\"})\n    }\n    return errors.Join(errs...)  \u002F\u002F nil if no errors, combined if any\n}\n\n\u002F\u002F Joined error's Error() prints all sub-errors:\n\u002F\u002F \"validation [name]: required\\nvalidation [email]: required\"\n\n\u002F\u002F errors.Is checks all joined errors:\n\u002F\u002F errors.Is(joinedErr, someErr) — true if any joined error matches\n",[18,2847,2848,2853,2858,2862,2883,2893,2906,2935,2939,2951,2975,2979,2993,3019,3023,3043,3047,3051,3056,3061,3065,3070],{"__ignoreMap":36},[40,2849,2850],{"class":42,"line":43},[40,2851,2852],{"class":46},"\u002F\u002F errors.Join combines multiple errors into one.\n",[40,2854,2855],{"class":42,"line":50},[40,2856,2857],{"class":46},"\u002F\u002F errors.Is\u002FAs walk all joined errors.\n",[40,2859,2860],{"class":42,"line":56},[40,2861,113],{"emptyLinePlaceholder":112},[40,2863,2864,2866,2869,2871,2873,2875,2877,2879,2881],{"class":42,"line":62},[40,2865,120],{"class":119},[40,2867,2868],{"class":123}," validateAll",[40,2870,128],{"class":127},[40,2872,163],{"class":131},[40,2874,150],{"class":119},[40,2876,168],{"class":123},[40,2878,171],{"class":127},[40,2880,174],{"class":119},[40,2882,177],{"class":127},[40,2884,2885,2887,2890],{"class":42,"line":68},[40,2886,790],{"class":119},[40,2888,2889],{"class":127}," errs []",[40,2891,2892],{"class":119},"error\n",[40,2894,2895,2897,2900,2902,2904],{"class":42,"line":74},[40,2896,208],{"class":119},[40,2898,2899],{"class":127}," u.Name ",[40,2901,1074],{"class":119},[40,2903,1077],{"class":198},[40,2905,177],{"class":127},[40,2907,2908,2911,2913,2916,2919,2921,2923,2925,2928,2930,2932],{"class":42,"line":80},[40,2909,2910],{"class":127},"        errs ",[40,2912,1434],{"class":119},[40,2914,2915],{"class":123}," append",[40,2917,2918],{"class":127},"(errs, ",[40,2920,819],{"class":119},[40,2922,990],{"class":123},[40,2924,1091],{"class":127},[40,2926,2927],{"class":198},"\"name\"",[40,2929,1097],{"class":127},[40,2931,1100],{"class":198},[40,2933,2934],{"class":127},"})\n",[40,2936,2937],{"class":42,"line":86},[40,2938,261],{"class":127},[40,2940,2941,2943,2945,2947,2949],{"class":42,"line":91},[40,2942,208],{"class":119},[40,2944,1071],{"class":127},[40,2946,1074],{"class":119},[40,2948,1077],{"class":198},[40,2950,177],{"class":127},[40,2952,2953,2955,2957,2959,2961,2963,2965,2967,2969,2971,2973],{"class":42,"line":97},[40,2954,2910],{"class":127},[40,2956,1434],{"class":119},[40,2958,2915],{"class":123},[40,2960,2918],{"class":127},[40,2962,819],{"class":119},[40,2964,990],{"class":123},[40,2966,1091],{"class":127},[40,2968,1094],{"class":198},[40,2970,1097],{"class":127},[40,2972,1100],{"class":198},[40,2974,2934],{"class":127},[40,2976,2977],{"class":42,"line":103},[40,2978,261],{"class":127},[40,2980,2981,2983,2986,2989,2991],{"class":42,"line":109},[40,2982,208],{"class":119},[40,2984,2985],{"class":127}," u.Age ",[40,2987,2988],{"class":119},"\u003C",[40,2990,1541],{"class":217},[40,2992,177],{"class":127},[40,2994,2995,2997,2999,3001,3003,3005,3007,3009,3012,3014,3017],{"class":42,"line":116},[40,2996,2910],{"class":127},[40,2998,1434],{"class":119},[40,3000,2915],{"class":123},[40,3002,2918],{"class":127},[40,3004,819],{"class":119},[40,3006,990],{"class":123},[40,3008,1091],{"class":127},[40,3010,3011],{"class":198},"\"age\"",[40,3013,1097],{"class":127},[40,3015,3016],{"class":198},"\"must be non-negative\"",[40,3018,2934],{"class":127},[40,3020,3021],{"class":42,"line":180},[40,3022,261],{"class":127},[40,3024,3025,3027,3029,3032,3035,3038,3040],{"class":42,"line":205},[40,3026,267],{"class":119},[40,3028,615],{"class":127},[40,3030,3031],{"class":123},"Join",[40,3033,3034],{"class":127},"(errs",[40,3036,3037],{"class":119},"...",[40,3039,1985],{"class":127},[40,3041,3042],{"class":46},"\u002F\u002F nil if no errors, combined if any\n",[40,3044,3045],{"class":42,"line":223},[40,3046,276],{"class":127},[40,3048,3049],{"class":42,"line":258},[40,3050,113],{"emptyLinePlaceholder":112},[40,3052,3053],{"class":42,"line":264},[40,3054,3055],{"class":46},"\u002F\u002F Joined error's Error() prints all sub-errors:\n",[40,3057,3058],{"class":42,"line":273},[40,3059,3060],{"class":46},"\u002F\u002F \"validation [name]: required\\nvalidation [email]: required\"\n",[40,3062,3063],{"class":42,"line":279},[40,3064,113],{"emptyLinePlaceholder":112},[40,3066,3067],{"class":42,"line":284},[40,3068,3069],{"class":46},"\u002F\u002F errors.Is checks all joined errors:\n",[40,3071,3072],{"class":42,"line":328},[40,3073,3074],{"class":46},"\u002F\u002F errors.Is(joinedErr, someErr) — true if any joined error matches\n",[23,3076,3078],{"id":3077},"tips-tricks","💡 Tips & Tricks",[3080,3081,3082,3094,3108,3126,3155,3170],"ul",{},[3083,3084,3085,3089,3090,3093],"li",{},[3086,3087,3088],"strong",{},"Idiom",": wrap errors with context at each layer — ",[18,3091,3092],{},"fmt.Errorf(\"doing X: %w\", err)"," — so the final error reads like a stack trace. The context should describe what the caller was doing, not the low-level failure.",[3083,3095,3096,3098,3099,3101,3102,3105,3106,138],{},[3086,3097,3088],{},": use ",[18,3100,409],{}," for sentinel errors — it walks the wrap chain. ",[18,3103,3104],{},"err == ErrNotFound"," misses wrapped errors. Always use ",[18,3107,409],{},[3083,3109,3110,3098,3112,3114,3115,3118,3119,3122,3123,138],{},[3086,3111,3088],{},[18,3113,741],{}," to extract typed errors with fields — ",[18,3116,3117],{},"var perr *PathError; if errors.As(err, &perr) { ... perr.Path }",". Pass ",[18,3120,3121],{},"&perr"," (pointer to the target), not ",[18,3124,3125],{},"perr",[3083,3127,3128,3098,3130,3132,3133,3135,3136,3139,3140,3142,3143,3145,3146,3148,3149,3151,3152,3154],{},[3086,3129,3088],{},[18,3131,246],{}," (not ",[18,3134,1331],{},") in ",[18,3137,3138],{},"fmt.Errorf"," to wrap — ",[18,3141,246],{}," preserves the original for ",[18,3144,618],{},"\u002F",[18,3147,813],{},"; ",[18,3150,1331],{}," formats into the message and loses the chain. Use ",[18,3153,1331],{}," only when you deliberately want to hide the original.",[3083,3156,3157,3159,3160,3162,3163,3165,3166,3169],{},[3086,3158,3088],{},": reserve ",[18,3161,2103],{}," for genuine bugs — violated invariants, \"this should never happen.\" Normal failures return ",[18,3164,174],{},". The ",[18,3167,3168],{},"MustX"," convention panics for programmer mistakes (bad compile-time constants).",[3083,3171,3172,3175,3176,3178],{},[3086,3173,3174],{},"Safety",": always ",[18,3177,2107],{}," in goroutines that handle external input — a single bad request causing a panic shouldn't crash the server. Log the panic + stack trace for debugging.",[23,3180,3182],{"id":3181},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[3080,3184,3185,3208,3228,3255,3274,3285,3295,3315,3324,3342],{},[3083,3186,3187,243,3194,3196,3197,3145,3199,3201,3202,3204,3205,3207],{},[3086,3188,3189,3191,3192],{},[18,3190,246],{}," vs ",[18,3193,1331],{},[18,3195,246],{}," wraps (preserves for ",[18,3198,618],{},[18,3200,813],{},"); ",[18,3203,1331],{}," formats (loses chain). Use ",[18,3206,246],{}," almost always.",[3083,3209,3210,243,3216,3218,3219,3221,3222,3225,3226,138],{},[3086,3211,3212,3213,3215],{},"Sentinel comparison with ",[18,3214,1074],{}," misses wraps",[18,3217,3104],{}," is false if ",[18,3220,780],{}," wraps ",[18,3223,3224],{},"ErrNotFound",". Use ",[18,3227,409],{},[3083,3229,3230,243,3238,3241,3242,982,3245,3248,3249,982,3252,2182],{},[3086,3231,3232,3234,3235],{},[18,3233,741],{}," target must be ",[18,3236,3237],{},"&target",[18,3239,3240],{},"var e *MyError; errors.As(err, &e)"," — pass ",[18,3243,3244],{},"&e",[18,3246,3247],{},"**MyError","), not ",[18,3250,3251],{},"e",[18,3253,3254],{},"*MyError",[3083,3256,3257,243,3260,3263,3264,3266,3267,3269,3270,3273],{},[3086,3258,3259],{},"Returning nil pointer as error = non-nil interface",[18,3261,3262],{},"var p *MyError = nil; return p"," — the returned ",[18,3265,174],{}," is non-nil (has type ",[18,3268,3254],{},"). Return ",[18,3271,3272],{},"nil"," directly.",[3083,3275,3276,3281,3282,3284],{},[3086,3277,3278,3280],{},[18,3279,2103],{}," in a goroutine crashes the program",": unrecovered panics terminate the whole process. Always ",[18,3283,2107],{}," in goroutines that handle external input.",[3083,3286,3287,3292,3293,138],{},[3086,3288,3289,3291],{},[18,3290,2107],{}," only works in the deferred function of the panicking goroutine",": you can't recover from another goroutine's panic. Each goroutine needs its own ",[18,3294,2107],{},[3083,3296,3297,3306,3307,3310,3311,3314],{},[3086,3298,3299,3302,3303],{},[18,3300,3301],{},"log.Fatal"," calls ",[18,3304,3305],{},"os.Exit",": skips deferred functions. Don't use it in library code or anywhere with cleanup ",[18,3308,3309],{},"defer","s. Use only in ",[18,3312,3313],{},"main"," for fatal startup errors.",[3083,3316,3317,3323],{},[3086,3318,3319,3322],{},[18,3320,3321],{},"errors.Join(nil, nil)"," returns nil",": joining nil errors returns nil. Joining non-nil errors returns a non-nil combined error.",[3083,3325,3326,3334,3335,3338,3339,3341],{},[3086,3327,3328,3329,3331,3332],{},"Multiple ",[18,3330,246],{}," in ",[18,3333,3138],{}," (Go 1.20+): ",[18,3336,3337],{},"fmt.Errorf(\"%w and %w\", err1, err2)"," wraps both — ",[18,3340,409],{}," checks both. Rare but supported.",[3083,3343,3344,243,3347,3350,3351,3354],{},[3086,3345,3346],{},"Ignoring an error is a bug",[18,3348,3349],{},"_ = f()"," hides failures. ",[18,3352,3353],{},"errcheck"," linter flags these. At minimum, log the error.",[23,3356,3358],{"id":3357},"quick-quiz","🧠 Quick Quiz",[28,3360,3361,3534],{"language":30},[32,3362,3364],{"className":34,"code":3363,"language":30,"meta":36,"style":36},"var ErrNotFound = errors.New(\"not found\")\n\nfunc getUser(id int) (*User, error) {\n    if id == 0 {\n        return nil, fmt.Errorf(\"getUser %d: %w\", id, ErrNotFound)\n    }\n    return &User{}, nil\n}\n\nfunc main() {\n    _, err := getUser(0)\n    fmt.Println(errors.Is(err, ErrNotFound))\n    fmt.Println(err == ErrNotFound)\n}\n",[18,3365,3366,3386,3390,3416,3428,3454,3458,3471,3475,3479,3488,3503,3517,3530],{"__ignoreMap":36},[40,3367,3368,3370,3373,3375,3377,3379,3381,3384],{"class":42,"line":43},[40,3369,1424],{"class":119},[40,3371,3372],{"class":127}," ErrNotFound ",[40,3374,1434],{"class":119},[40,3376,615],{"class":127},[40,3378,1439],{"class":123},[40,3380,128],{"class":127},[40,3382,3383],{"class":198},"\"not found\"",[40,3385,473],{"class":127},[40,3387,3388],{"class":42,"line":50},[40,3389,113],{"emptyLinePlaceholder":112},[40,3391,3392,3394,3397,3399,3401,3404,3406,3408,3410,3412,3414],{"class":42,"line":56},[40,3393,120],{"class":119},[40,3395,3396],{"class":123}," getUser",[40,3398,128],{"class":127},[40,3400,1512],{"class":131},[40,3402,3403],{"class":119}," int",[40,3405,1518],{"class":127},[40,3407,796],{"class":119},[40,3409,168],{"class":123},[40,3411,144],{"class":127},[40,3413,174],{"class":119},[40,3415,505],{"class":127},[40,3417,3418,3420,3422,3424,3426],{"class":42,"line":62},[40,3419,208],{"class":119},[40,3421,1535],{"class":127},[40,3423,1074],{"class":119},[40,3425,1541],{"class":217},[40,3427,177],{"class":127},[40,3429,3430,3432,3434,3436,3438,3440,3443,3445,3447,3449,3451],{"class":42,"line":68},[40,3431,226],{"class":119},[40,3433,218],{"class":217},[40,3435,542],{"class":127},[40,3437,232],{"class":123},[40,3439,128],{"class":127},[40,3441,3442],{"class":198},"\"getUser ",[40,3444,240],{"class":217},[40,3446,243],{"class":198},[40,3448,246],{"class":217},[40,3450,249],{"class":198},[40,3452,3453],{"class":127},", id, ErrNotFound)\n",[40,3455,3456],{"class":42,"line":74},[40,3457,261],{"class":127},[40,3459,3460,3462,3464,3466,3469],{"class":42,"line":80},[40,3461,267],{"class":119},[40,3463,1086],{"class":119},[40,3465,168],{"class":123},[40,3467,3468],{"class":127},"{}, ",[40,3470,575],{"class":217},[40,3472,3473],{"class":42,"line":86},[40,3474,276],{"class":127},[40,3476,3477],{"class":42,"line":91},[40,3478,113],{"emptyLinePlaceholder":112},[40,3480,3481,3483,3486],{"class":42,"line":97},[40,3482,120],{"class":119},[40,3484,3485],{"class":123}," main",[40,3487,1952],{"class":127},[40,3489,3490,3492,3494,3496,3498,3501],{"class":42,"line":103},[40,3491,183],{"class":127},[40,3493,186],{"class":119},[40,3495,3396],{"class":123},[40,3497,128],{"class":127},[40,3499,3500],{"class":217},"0",[40,3502,473],{"class":127},[40,3504,3505,3507,3509,3512,3514],{"class":42,"line":109},[40,3506,1973],{"class":127},[40,3508,629],{"class":123},[40,3510,3511],{"class":127},"(errors.",[40,3513,618],{"class":123},[40,3515,3516],{"class":127},"(err, ErrNotFound))\n",[40,3518,3519,3521,3523,3525,3527],{"class":42,"line":116},[40,3520,1973],{"class":127},[40,3522,629],{"class":123},[40,3524,1978],{"class":127},[40,3526,1074],{"class":119},[40,3528,3529],{"class":127}," ErrNotFound)\n",[40,3531,3532],{"class":42,"line":180},[40,3533,276],{"class":127},[14,3535,3536],{},"What's printed?",[3538,3539,3540,3544,3552,3592],"details",{},[3541,3542,3543],"summary",{},"Answer",[32,3545,3550],{"className":3546,"code":3548,"language":3549},[3547],"language-text","true\nfalse\n","text",[18,3551,3548],{"__ignoreMap":36},[3080,3553,3554,3572],{},[3083,3555,3556,3559,3560,3562,3563,3565,3566,3568,3569,3571],{},[18,3557,3558],{},"errors.Is(err, ErrNotFound)"," → ",[18,3561,1966],{}," — ",[18,3564,409],{}," walks the wrap chain and finds ",[18,3567,3224],{}," inside the ",[18,3570,3138],{},"-wrapped error.",[3083,3573,3574,3559,3576,3562,3579,3581,3582,3585,3586,3588,3589,3591],{},[18,3575,3104],{},[18,3577,3578],{},"false",[18,3580,780],{}," is ",[18,3583,3584],{},"\"getUser 0: not found\""," (a wrapped error), not ",[18,3587,3224],{}," itself. Direct ",[18,3590,1074],{}," only matches the top-level error, not wrapped ones.",[14,3593,3594,3595,3597,3598,3600,3601,3603],{},"This is why you must use ",[18,3596,409],{}," instead of ",[18,3599,1074],{}," for sentinel comparison — ",[18,3602,1074],{}," misses wrapped errors, and all errors in production code should be wrapped (to add context).",[23,3605,3607],{"id":3606},"whats-next","📚 What's Next",[14,3609,3610,3611,3616],{},"→ ",[3612,3613,3615],"a",{"href":3614},"\u002Fgo\u002F16-goroutines","16 — Goroutines"," — goroutine scheduling, GOMAXPROCS, leak prevention, and the preemptive scheduler.",[3618,3619,3620],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}",{"title":36,"searchDepth":50,"depth":50,"links":3622},[3623,3624,3626],{"id":25,"depth":50,"text":26},{"id":406,"depth":50,"text":3625},"errors.Is — Sentinel Error Matching",{"id":3606,"depth":50,"text":3607},"Error wrapping chains, errors.Is\u002FAs semantics, sentinel vs typed errors, the nil-interface trap on errors, and the panic-vs-error decision.","md",{},"\u002Fgo\u002F15-error-handling",{"title":5,"description":3627},"go\u002F15-error-handling","3MQzkXAkw3mcVfCa5EhX5hOeT0SBzqxGHI-X47CWs04",1789924648549]