[{"data":1,"prerenderedAt":4476},["ShallowReactive",2],{"content-\u002F02-functions-errors\u002F03-errors":3},{"id":4,"title":5,"body":6,"description":94,"difficulty":4464,"extension":4465,"meta":4466,"module":4467,"navigation":191,"next":4468,"order":125,"path":4469,"prev":4470,"seo":4471,"slug":4218,"stem":4472,"tags":4473,"__hash__":4475},"content\u002F02-functions-errors\u002F03-errors\u002Findex.md","Ошибки в Go",{"type":7,"value":8,"toc":4421},"minimark",[9,80,83,88,142,149,153,226,229,233,396,400,482,486,598,601,603,643,645,649,798,804,808,869,872,876,970,973,984,988,1036,1039,1043,1057,1059,1096,1098,1102,1276,1298,1302,1452,1461,1465,1660,1666,1673,1677,1756,1769,1771,1811,1813,1817,1917,1925,1929,2150,2155,2159,2241,2244,2248,2342,2345,2349,2381,2384,2386,2419,2421,2425,2456,2460,2770,2774,2784,2788,2863,2865,2904,2906,2910,3034,3038,3046,3050,3119,3128,3132,3200,3204,3214,3218,3221,3223,3257,3259,3263,3329,3332,3336,3510,3513,3517,3603,3607,3762,3765,3769,3772,3774,3814,3816,3820,3937,3940,3946,3950,4086,4091,4095,4209,4211,4241,4243,4247,4274,4281,4285,4374,4384,4388,4394,4404,4408,4415,4417],[10,11,12,23,34,42,61,73],"ul",{},[13,14,15,19,20],"li",{},[16,17,18],"code",{},"error"," — встроенный интерфейс с единственным методом ",[16,21,22],{},"Error() string",[13,24,25,26,29,30,33],{},"создание: ",[16,27,28],{},"errors.New(\"text\")"," — простая ошибка; ",[16,31,32],{},"fmt.Errorf(\"... %v\", arg)"," — с форматированием",[13,35,36,37,41],{},"ошибку ",[38,39,40],"strong",{},"возвращают последней"," из функции — соглашение Go",[13,43,44,45,48,49,52,53,56,57,60],{},"именование переменных: начинаются с ",[16,46,47],{},"Err","\u002F",[16,50,51],{},"err"," (",[16,54,55],{},"ErrNotFound",", ",[16,58,59],{},"errInternal",")",[13,62,63,64,52,67,56,70,60],{},"именование типов: заканчиваются на ",[16,65,66],{},"Error",[16,68,69],{},"PathError",[16,71,72],{},"SyntaxError",[13,74,75,76,79],{},"текст ошибки — ",[38,77,78],{},"с маленькой буквы"," (соглашение гошного коммьюнити)",[81,82],"hr",{},[84,85,87],"h2",{"id":86},"интерфейс","Интерфейс",[89,90,95],"pre",{"className":91,"code":92,"language":93,"meta":94,"style":94},"language-go shiki shiki-themes github-dark","\u002F\u002F builtin\ntype error interface {\n    Error() string\n}\n","go","",[16,96,97,106,123,136],{"__ignoreMap":94},[98,99,102],"span",{"class":100,"line":101},"line",1,[98,103,105],{"class":104},"sAwPA","\u002F\u002F builtin\n",[98,107,109,113,116,119],{"class":100,"line":108},2,[98,110,112],{"class":111},"snl16","type",[98,114,115],{"class":111}," error",[98,117,118],{"class":111}," interface",[98,120,122],{"class":121},"s95oV"," {\n",[98,124,126,130,133],{"class":100,"line":125},3,[98,127,129],{"class":128},"svObZ","    Error",[98,131,132],{"class":121},"() ",[98,134,135],{"class":111},"string\n",[98,137,139],{"class":100,"line":138},4,[98,140,141],{"class":121},"}\n",[143,144,145,146,148],"p",{},"Любой тип с методом ",[16,147,22],{}," реализует этот интерфейс.",[84,150,152],{"id":151},"создание-ошибок","Создание ошибок",[89,154,156],{"className":91,"code":155,"language":93,"meta":94,"style":94},"\u002F\u002F Простая ошибка — достаточно в большинстве случаев\nerr := errors.New(\"connection refused\")\n\n\u002F\u002F С форматированием — когда нужны параметры в тексте\nerr := fmt.Errorf(\"user %d not found\", userID)\n",[16,157,158,163,187,193,198],{"__ignoreMap":94},[98,159,160],{"class":100,"line":101},[98,161,162],{"class":104},"\u002F\u002F Простая ошибка — достаточно в большинстве случаев\n",[98,164,165,168,171,174,177,180,184],{"class":100,"line":108},[98,166,167],{"class":121},"err ",[98,169,170],{"class":111},":=",[98,172,173],{"class":121}," errors.",[98,175,176],{"class":128},"New",[98,178,179],{"class":121},"(",[98,181,183],{"class":182},"sU2Wk","\"connection refused\"",[98,185,186],{"class":121},")\n",[98,188,189],{"class":100,"line":125},[98,190,192],{"emptyLinePlaceholder":191},true,"\n",[98,194,195],{"class":100,"line":138},[98,196,197],{"class":104},"\u002F\u002F С форматированием — когда нужны параметры в тексте\n",[98,199,201,203,205,208,211,213,216,220,223],{"class":100,"line":200},5,[98,202,167],{"class":121},[98,204,170],{"class":111},[98,206,207],{"class":121}," fmt.",[98,209,210],{"class":128},"Errorf",[98,212,179],{"class":121},[98,214,215],{"class":182},"\"user ",[98,217,219],{"class":218},"sDLfK","%d",[98,221,222],{"class":182}," not found\"",[98,224,225],{"class":121},", userID)\n",[143,227,228],{},"На практике чаще всего используют именно эти два способа — отдельные типы нужны редко.",[84,230,232],{"id":231},"соглашения-именования","Соглашения именования",[89,234,236],{"className":91,"code":235,"language":93,"meta":94,"style":94},"\u002F\u002F Переменные — начинаются с Err\u002Ferr\nvar ErrNotFound = errors.New(\"not found\")     \u002F\u002F публичная\nvar errInternal = errors.New(\"internal error\") \u002F\u002F приватная\n\n\u002F\u002F Типы — заканчиваются на Error\ntype PathError struct { Op, Path string; Err error }\ntype SyntaxError struct { Msg string; Offset int64 }\n\n\u002F\u002F Текст — с маленькой буквы\nerrors.New(\"connection refused\")  \u002F\u002F ✅\nerrors.New(\"Connection refused\")  \u002F\u002F ❌\n",[16,237,238,243,269,293,297,302,327,350,355,361,379],{"__ignoreMap":94},[98,239,240],{"class":100,"line":101},[98,241,242],{"class":104},"\u002F\u002F Переменные — начинаются с Err\u002Ferr\n",[98,244,245,248,251,254,256,258,260,263,266],{"class":100,"line":108},[98,246,247],{"class":111},"var",[98,249,250],{"class":121}," ErrNotFound ",[98,252,253],{"class":111},"=",[98,255,173],{"class":121},[98,257,176],{"class":128},[98,259,179],{"class":121},[98,261,262],{"class":182},"\"not found\"",[98,264,265],{"class":121},")     ",[98,267,268],{"class":104},"\u002F\u002F публичная\n",[98,270,271,273,276,278,280,282,284,287,290],{"class":100,"line":125},[98,272,247],{"class":111},[98,274,275],{"class":121}," errInternal ",[98,277,253],{"class":111},[98,279,173],{"class":121},[98,281,176],{"class":128},[98,283,179],{"class":121},[98,285,286],{"class":182},"\"internal error\"",[98,288,289],{"class":121},") ",[98,291,292],{"class":104},"\u002F\u002F приватная\n",[98,294,295],{"class":100,"line":138},[98,296,192],{"emptyLinePlaceholder":191},[98,298,299],{"class":100,"line":200},[98,300,301],{"class":104},"\u002F\u002F Типы — заканчиваются на Error\n",[98,303,305,307,310,313,316,319,322,324],{"class":100,"line":304},6,[98,306,112],{"class":111},[98,308,309],{"class":128}," PathError",[98,311,312],{"class":111}," struct",[98,314,315],{"class":121}," { Op, Path ",[98,317,318],{"class":111},"string",[98,320,321],{"class":121},"; Err ",[98,323,18],{"class":111},[98,325,326],{"class":121}," }\n",[98,328,330,332,335,337,340,342,345,348],{"class":100,"line":329},7,[98,331,112],{"class":111},[98,333,334],{"class":128}," SyntaxError",[98,336,312],{"class":111},[98,338,339],{"class":121}," { Msg ",[98,341,318],{"class":111},[98,343,344],{"class":121},"; Offset ",[98,346,347],{"class":111},"int64",[98,349,326],{"class":121},[98,351,353],{"class":100,"line":352},8,[98,354,192],{"emptyLinePlaceholder":191},[98,356,358],{"class":100,"line":357},9,[98,359,360],{"class":104},"\u002F\u002F Текст — с маленькой буквы\n",[98,362,364,367,369,371,373,376],{"class":100,"line":363},10,[98,365,366],{"class":121},"errors.",[98,368,176],{"class":128},[98,370,179],{"class":121},[98,372,183],{"class":182},[98,374,375],{"class":121},")  ",[98,377,378],{"class":104},"\u002F\u002F ✅\n",[98,380,382,384,386,388,391,393],{"class":100,"line":381},11,[98,383,366],{"class":121},[98,385,176],{"class":128},[98,387,179],{"class":121},[98,389,390],{"class":182},"\"Connection refused\"",[98,392,375],{"class":121},[98,394,395],{"class":104},"\u002F\u002F ❌\n",[84,397,399],{"id":398},"ошибка-последнее-возвращаемое-значение","Ошибка — последнее возвращаемое значение",[89,401,403],{"className":91,"code":402,"language":93,"meta":94,"style":94},"\u002F\u002F ✅ принято в Go\nfunc ReadFile(path string) ([]byte, error) { ... }\n\n\u002F\u002F ❌ непривычно, ломает ожидания\nfunc ReadFile(path string) (error, []byte) { ... }\n",[16,404,405,410,445,449,454],{"__ignoreMap":94},[98,406,407],{"class":100,"line":101},[98,408,409],{"class":104},"\u002F\u002F ✅ принято в Go\n",[98,411,412,415,418,420,424,427,430,433,435,437,440,443],{"class":100,"line":108},[98,413,414],{"class":111},"func",[98,416,417],{"class":128}," ReadFile",[98,419,179],{"class":121},[98,421,423],{"class":422},"s9osk","path",[98,425,426],{"class":111}," string",[98,428,429],{"class":121},") ([]",[98,431,432],{"class":111},"byte",[98,434,56],{"class":121},[98,436,18],{"class":111},[98,438,439],{"class":121},") { ",[98,441,442],{"class":111},"...",[98,444,326],{"class":121},[98,446,447],{"class":100,"line":125},[98,448,192],{"emptyLinePlaceholder":191},[98,450,451],{"class":100,"line":138},[98,452,453],{"class":104},"\u002F\u002F ❌ непривычно, ломает ожидания\n",[98,455,456,458,460,462,464,466,469,471,474,476,478,480],{"class":100,"line":200},[98,457,414],{"class":111},[98,459,417],{"class":128},[98,461,179],{"class":121},[98,463,423],{"class":422},[98,465,426],{"class":111},[98,467,468],{"class":121},") (",[98,470,18],{"class":111},[98,472,473],{"class":121},", []",[98,475,432],{"class":111},[98,477,439],{"class":121},[98,479,442],{"class":111},[98,481,326],{"class":121},[84,483,485],{"id":484},"антипаттерн-лишнее-проксирование","Антипаттерн: лишнее проксирование",[89,487,489],{"className":91,"code":488,"language":93,"meta":94,"style":94},"\u002F\u002F ❌ бессмысленный код — ничего не меняет\nfunc DoSomething() error {\n    err := doWork()\n    if err != nil {\n        return err\n    }\n    return nil\n}\n\n\u002F\u002F ✅ эквивалентно и проще\nfunc DoSomething() error {\n    return doWork()\n}\n",[16,490,491,496,509,522,538,546,551,559,563,567,572,584,593],{"__ignoreMap":94},[98,492,493],{"class":100,"line":101},[98,494,495],{"class":104},"\u002F\u002F ❌ бессмысленный код — ничего не меняет\n",[98,497,498,500,503,505,507],{"class":100,"line":108},[98,499,414],{"class":111},[98,501,502],{"class":128}," DoSomething",[98,504,132],{"class":121},[98,506,18],{"class":111},[98,508,122],{"class":121},[98,510,511,514,516,519],{"class":100,"line":125},[98,512,513],{"class":121},"    err ",[98,515,170],{"class":111},[98,517,518],{"class":128}," doWork",[98,520,521],{"class":121},"()\n",[98,523,524,527,530,533,536],{"class":100,"line":138},[98,525,526],{"class":111},"    if",[98,528,529],{"class":121}," err ",[98,531,532],{"class":111},"!=",[98,534,535],{"class":218}," nil",[98,537,122],{"class":121},[98,539,540,543],{"class":100,"line":200},[98,541,542],{"class":111},"        return",[98,544,545],{"class":121}," err\n",[98,547,548],{"class":100,"line":304},[98,549,550],{"class":121},"    }\n",[98,552,553,556],{"class":100,"line":329},[98,554,555],{"class":111},"    return",[98,557,558],{"class":218}," nil\n",[98,560,561],{"class":100,"line":352},[98,562,141],{"class":121},[98,564,565],{"class":100,"line":357},[98,566,192],{"emptyLinePlaceholder":191},[98,568,569],{"class":100,"line":363},[98,570,571],{"class":104},"\u002F\u002F ✅ эквивалентно и проще\n",[98,573,574,576,578,580,582],{"class":100,"line":381},[98,575,414],{"class":111},[98,577,502],{"class":128},[98,579,132],{"class":121},[98,581,18],{"class":111},[98,583,122],{"class":121},[98,585,587,589,591],{"class":100,"line":586},12,[98,588,555],{"class":111},[98,590,518],{"class":128},[98,592,521],{"class":121},[98,594,596],{"class":100,"line":595},13,[98,597,141],{"class":121},[143,599,600],{},"Если сигнатуры совпадают и контекст не нужен — просто проксируй.",[81,602],{},[10,604,605,612,619,626,633],{},[13,606,607,608,611],{},"sentinel (дозорная) ошибка — ",[38,609,610],{},"глобальная переменная"," для маркировки конкретной ситуации",[13,613,614,615,618],{},"нужна, чтобы понять: произошла ли ",[38,616,617],{},"именно эта"," ошибка (sql.ErrNoRows, io.EOF)",[13,620,621,622,625],{},"проблема: глобальную переменную ",[38,623,624],{},"можно изменить"," из другого пакета",[13,627,628,629,632],{},"решение: ",[38,630,631],{},"константные ошибки"," — type definition от string + метод Error()",[13,634,635,636,56,639,642],{},"сравнение через ",[16,637,638],{},"errors.Is",[38,640,641],{},"не"," через ==",[81,644],{},[84,646,648],{"id":647},"sentinel-ошибка","Sentinel ошибка",[89,650,652],{"className":91,"code":651,"language":93,"meta":94,"style":94},"\u002F\u002F Зарезервированная глобальная ошибка\nvar ErrDatabaseProblem = errors.New(\"database problem\")\n\n\u002F\u002F Использование\nfunc QueryUser(id int) (*User, error) {\n    row := db.QueryRow(\"SELECT ...\", id)\n    err := row.Scan(&user)\n    if err == sql.ErrNoRows {\n        return nil, ErrDatabaseProblem\n    }\n    return &user, err\n}\n",[16,653,654,659,679,683,688,718,739,759,771,780,784,794],{"__ignoreMap":94},[98,655,656],{"class":100,"line":101},[98,657,658],{"class":104},"\u002F\u002F Зарезервированная глобальная ошибка\n",[98,660,661,663,666,668,670,672,674,677],{"class":100,"line":108},[98,662,247],{"class":111},[98,664,665],{"class":121}," ErrDatabaseProblem ",[98,667,253],{"class":111},[98,669,173],{"class":121},[98,671,176],{"class":128},[98,673,179],{"class":121},[98,675,676],{"class":182},"\"database problem\"",[98,678,186],{"class":121},[98,680,681],{"class":100,"line":125},[98,682,192],{"emptyLinePlaceholder":191},[98,684,685],{"class":100,"line":138},[98,686,687],{"class":104},"\u002F\u002F Использование\n",[98,689,690,692,695,697,700,703,705,708,711,713,715],{"class":100,"line":200},[98,691,414],{"class":111},[98,693,694],{"class":128}," QueryUser",[98,696,179],{"class":121},[98,698,699],{"class":422},"id",[98,701,702],{"class":111}," int",[98,704,468],{"class":121},[98,706,707],{"class":111},"*",[98,709,710],{"class":128},"User",[98,712,56],{"class":121},[98,714,18],{"class":111},[98,716,717],{"class":121},") {\n",[98,719,720,723,725,728,731,733,736],{"class":100,"line":304},[98,721,722],{"class":121},"    row ",[98,724,170],{"class":111},[98,726,727],{"class":121}," db.",[98,729,730],{"class":128},"QueryRow",[98,732,179],{"class":121},[98,734,735],{"class":182},"\"SELECT ...\"",[98,737,738],{"class":121},", id)\n",[98,740,741,743,745,748,751,753,756],{"class":100,"line":329},[98,742,513],{"class":121},[98,744,170],{"class":111},[98,746,747],{"class":121}," row.",[98,749,750],{"class":128},"Scan",[98,752,179],{"class":121},[98,754,755],{"class":111},"&",[98,757,758],{"class":121},"user)\n",[98,760,761,763,765,768],{"class":100,"line":352},[98,762,526],{"class":111},[98,764,529],{"class":121},[98,766,767],{"class":111},"==",[98,769,770],{"class":121}," sql.ErrNoRows {\n",[98,772,773,775,777],{"class":100,"line":357},[98,774,542],{"class":111},[98,776,535],{"class":218},[98,778,779],{"class":121},", ErrDatabaseProblem\n",[98,781,782],{"class":100,"line":363},[98,783,550],{"class":121},[98,785,786,788,791],{"class":100,"line":381},[98,787,555],{"class":111},[98,789,790],{"class":111}," &",[98,792,793],{"class":121},"user, err\n",[98,795,796],{"class":100,"line":586},[98,797,141],{"class":121},[143,799,800,803],{},[16,801,802],{},"sql.ErrNoRows"," — классический пример sentinel: нет строк, запрос пуст.",[84,805,807],{"id":806},"проблема-мутабельность","Проблема: мутабельность",[89,809,811],{"className":91,"code":810,"language":93,"meta":94,"style":94},"\u002F\u002F Кто угодно может поменять глобальную ошибку!\nprevEOF := io.EOF\nio.EOF = errors.New(\"hacked!\")\n\nfmt.Println(io.EOF == prevEOF)  \u002F\u002F false — сломали!\n",[16,812,813,818,828,846,850],{"__ignoreMap":94},[98,814,815],{"class":100,"line":101},[98,816,817],{"class":104},"\u002F\u002F Кто угодно может поменять глобальную ошибку!\n",[98,819,820,823,825],{"class":100,"line":108},[98,821,822],{"class":121},"prevEOF ",[98,824,170],{"class":111},[98,826,827],{"class":121}," io.EOF\n",[98,829,830,833,835,837,839,841,844],{"class":100,"line":125},[98,831,832],{"class":121},"io.EOF ",[98,834,253],{"class":111},[98,836,173],{"class":121},[98,838,176],{"class":128},[98,840,179],{"class":121},[98,842,843],{"class":182},"\"hacked!\"",[98,845,186],{"class":121},[98,847,848],{"class":100,"line":138},[98,849,192],{"emptyLinePlaceholder":191},[98,851,852,855,858,861,863,866],{"class":100,"line":200},[98,853,854],{"class":121},"fmt.",[98,856,857],{"class":128},"Println",[98,859,860],{"class":121},"(io.EOF ",[98,862,767],{"class":111},[98,864,865],{"class":121}," prevEOF)  ",[98,867,868],{"class":104},"\u002F\u002F false — сломали!\n",[143,870,871],{},"Никто так не делает, но технически возможно.",[84,873,875],{"id":874},"решение-константные-ошибки","Решение: константные ошибки",[89,877,879],{"className":91,"code":878,"language":93,"meta":94,"style":94},"type ConstError string\n\nfunc (e ConstError) Error() string {\n    return string(e)  \u002F\u002F type definition → явное приведение\n}\n\nconst ErrDatabase ConstError = \"database problem\"\n\n\u002F\u002F Нельзя изменить:\n\u002F\u002F ErrDatabase = \"other\"  \u002F\u002F ❌ ошибка компиляции — константу менять нельзя\n",[16,880,881,891,895,917,929,933,937,953,957,962],{"__ignoreMap":94},[98,882,883,885,888],{"class":100,"line":101},[98,884,112],{"class":111},[98,886,887],{"class":128}," ConstError",[98,889,890],{"class":111}," string\n",[98,892,893],{"class":100,"line":108},[98,894,192],{"emptyLinePlaceholder":191},[98,896,897,899,901,904,907,909,911,913,915],{"class":100,"line":125},[98,898,414],{"class":111},[98,900,52],{"class":121},[98,902,903],{"class":422},"e ",[98,905,906],{"class":128},"ConstError",[98,908,289],{"class":121},[98,910,66],{"class":128},[98,912,132],{"class":121},[98,914,318],{"class":111},[98,916,122],{"class":121},[98,918,919,921,923,926],{"class":100,"line":138},[98,920,555],{"class":111},[98,922,426],{"class":111},[98,924,925],{"class":121},"(e)  ",[98,927,928],{"class":104},"\u002F\u002F type definition → явное приведение\n",[98,930,931],{"class":100,"line":200},[98,932,141],{"class":121},[98,934,935],{"class":100,"line":304},[98,936,192],{"emptyLinePlaceholder":191},[98,938,939,942,945,947,950],{"class":100,"line":329},[98,940,941],{"class":111},"const",[98,943,944],{"class":218}," ErrDatabase",[98,946,887],{"class":128},[98,948,949],{"class":111}," =",[98,951,952],{"class":182}," \"database problem\"\n",[98,954,955],{"class":100,"line":352},[98,956,192],{"emptyLinePlaceholder":191},[98,958,959],{"class":100,"line":357},[98,960,961],{"class":104},"\u002F\u002F Нельзя изменить:\n",[98,963,964,967],{"class":100,"line":363},[98,965,966],{"class":104},"\u002F\u002F ErrDatabase = \"other\"",[98,968,969],{"class":104},"  \u002F\u002F ❌ ошибка компиляции — константу менять нельзя\n",[143,971,972],{},"Хак: type definition от string → можно сделать константой. Метод Error() делает совместимым с интерфейсом error.",[143,974,975,976,979,980,983],{},"Почему нужно явное приведение ",[16,977,978],{},"string(e)"," в методе Error(): type definition создаёт новый тип, не псевдоним, поэтому прямое использование ",[16,981,982],{},"e"," в строковом контексте запрещено.",[84,985,987],{"id":986},"оборачивание-работает","Оборачивание работает",[89,989,991],{"className":91,"code":990,"language":93,"meta":94,"style":94},"wrapped := fmt.Errorf(\"query failed: %w\", ErrDatabase)\nfmt.Println(errors.Is(wrapped, ErrDatabase))  \u002F\u002F true\n",[16,992,993,1018],{"__ignoreMap":94},[98,994,995,998,1000,1002,1004,1006,1009,1012,1015],{"class":100,"line":101},[98,996,997],{"class":121},"wrapped ",[98,999,170],{"class":111},[98,1001,207],{"class":121},[98,1003,210],{"class":128},[98,1005,179],{"class":121},[98,1007,1008],{"class":182},"\"query failed: ",[98,1010,1011],{"class":218},"%w",[98,1013,1014],{"class":182},"\"",[98,1016,1017],{"class":121},", ErrDatabase)\n",[98,1019,1020,1022,1024,1027,1030,1033],{"class":100,"line":108},[98,1021,854],{"class":121},[98,1023,857],{"class":128},[98,1025,1026],{"class":121},"(errors.",[98,1028,1029],{"class":128},"Is",[98,1031,1032],{"class":121},"(wrapped, ErrDatabase))  ",[98,1034,1035],{"class":104},"\u002F\u002F true\n",[143,1037,1038],{},"ConstError — обычная ошибка, errors.Is\u002FAs\u002FUnwrap работают.",[84,1040,1042],{"id":1041},"почему-не-сравнивать-через","Почему не сравнивать через ==",[143,1044,1045,1046,1049,1050,1053,1054,1056],{},"Если ошибка была обёрнута через ",[16,1047,1048],{},"fmt.Errorf(\"%w\", sentinel)",", прямое сравнение ",[16,1051,1052],{},"err == sentinel"," вернёт false. ",[16,1055,638],{}," раскручивает цепочку обёрток.",[81,1058],{},[10,1060,1061,1064,1074,1083,1089],{},[13,1062,1063],{},"оборачивание = упаковка ошибки в контейнер-обёртку, сохраняя доступ к исходной",[13,1065,1066,1069,1070,1073],{},[16,1067,1068],{},"%v"," — просто вставляет текст, Unwrap ",[38,1071,1072],{},"не работает"," (нет метода Unwrap)",[13,1075,1076,1078,1079,1082],{},[16,1077,1011],{}," (Go 1.13+) — создаёт тип с методом ",[16,1080,1081],{},"Unwrap()",", цепочка разворачивания работает",[13,1084,1085,1088],{},[16,1086,1087],{},"errors.Unwrap()"," — вызывает метод Unwrap, возвращает внутреннюю ошибку (или nil)",[13,1090,1091,1092,1095],{},"зачем: добавить контекст при пробросе ",[38,1093,1094],{},"или"," пометить ошибку sentinel'ом",[81,1097],{},[84,1099,1101],{"id":1100},"v-vs-w","%v vs %w",[89,1103,1105],{"className":91,"code":1104,"language":93,"meta":94,"style":94},"original := errors.New(\"connection refused\")\n\n\u002F\u002F %v — просто текст, unwrap НЕ работает\n\u002F\u002F (такая же структра создается и для errors.New())\n\u002F\u002Ftype errorString struct {\n\u002F\u002F    s string\n\u002F\u002F} \n\nwrapped1 := fmt.Errorf(\"db error: %v\", original)\nfmt.Println(errors.Unwrap(wrapped1))  \u002F\u002F \u003Cnil> — нечего разворачивать\n\n\u002F\u002F %w — создаёт обёртку с Unwrap(), цепочка работает\n\u002F\u002Ftype wrapError struct {\n\u002F\u002F    msg   string\n\u002F\u002F    err   error \u002F\u002F Здесь хранится ссылка на оригинальную ошибку\n\u002F\u002F}\n\nwrapped2 := fmt.Errorf(\"db error: %w\", original)\nfmt.Println(errors.Unwrap(wrapped2))  \u002F\u002F \"connection refused\"\n",[16,1106,1107,1124,1128,1133,1138,1143,1148,1153,1157,1180,1197,1201,1206,1211,1217,1226,1232,1237,1259],{"__ignoreMap":94},[98,1108,1109,1112,1114,1116,1118,1120,1122],{"class":100,"line":101},[98,1110,1111],{"class":121},"original ",[98,1113,170],{"class":111},[98,1115,173],{"class":121},[98,1117,176],{"class":128},[98,1119,179],{"class":121},[98,1121,183],{"class":182},[98,1123,186],{"class":121},[98,1125,1126],{"class":100,"line":108},[98,1127,192],{"emptyLinePlaceholder":191},[98,1129,1130],{"class":100,"line":125},[98,1131,1132],{"class":104},"\u002F\u002F %v — просто текст, unwrap НЕ работает\n",[98,1134,1135],{"class":100,"line":138},[98,1136,1137],{"class":104},"\u002F\u002F (такая же структра создается и для errors.New())\n",[98,1139,1140],{"class":100,"line":200},[98,1141,1142],{"class":104},"\u002F\u002Ftype errorString struct {\n",[98,1144,1145],{"class":100,"line":304},[98,1146,1147],{"class":104},"\u002F\u002F    s string\n",[98,1149,1150],{"class":100,"line":329},[98,1151,1152],{"class":104},"\u002F\u002F} \n",[98,1154,1155],{"class":100,"line":352},[98,1156,192],{"emptyLinePlaceholder":191},[98,1158,1159,1162,1164,1166,1168,1170,1173,1175,1177],{"class":100,"line":357},[98,1160,1161],{"class":121},"wrapped1 ",[98,1163,170],{"class":111},[98,1165,207],{"class":121},[98,1167,210],{"class":128},[98,1169,179],{"class":121},[98,1171,1172],{"class":182},"\"db error: ",[98,1174,1068],{"class":218},[98,1176,1014],{"class":182},[98,1178,1179],{"class":121},", original)\n",[98,1181,1182,1184,1186,1188,1191,1194],{"class":100,"line":363},[98,1183,854],{"class":121},[98,1185,857],{"class":128},[98,1187,1026],{"class":121},[98,1189,1190],{"class":128},"Unwrap",[98,1192,1193],{"class":121},"(wrapped1))  ",[98,1195,1196],{"class":104},"\u002F\u002F \u003Cnil> — нечего разворачивать\n",[98,1198,1199],{"class":100,"line":381},[98,1200,192],{"emptyLinePlaceholder":191},[98,1202,1203],{"class":100,"line":586},[98,1204,1205],{"class":104},"\u002F\u002F %w — создаёт обёртку с Unwrap(), цепочка работает\n",[98,1207,1208],{"class":100,"line":595},[98,1209,1210],{"class":104},"\u002F\u002Ftype wrapError struct {\n",[98,1212,1214],{"class":100,"line":1213},14,[98,1215,1216],{"class":104},"\u002F\u002F    msg   string\n",[98,1218,1220,1223],{"class":100,"line":1219},15,[98,1221,1222],{"class":104},"\u002F\u002F    err   error",[98,1224,1225],{"class":104}," \u002F\u002F Здесь хранится ссылка на оригинальную ошибку\n",[98,1227,1229],{"class":100,"line":1228},16,[98,1230,1231],{"class":104},"\u002F\u002F}\n",[98,1233,1235],{"class":100,"line":1234},17,[98,1236,192],{"emptyLinePlaceholder":191},[98,1238,1240,1243,1245,1247,1249,1251,1253,1255,1257],{"class":100,"line":1239},18,[98,1241,1242],{"class":121},"wrapped2 ",[98,1244,170],{"class":111},[98,1246,207],{"class":121},[98,1248,210],{"class":128},[98,1250,179],{"class":121},[98,1252,1172],{"class":182},[98,1254,1011],{"class":218},[98,1256,1014],{"class":182},[98,1258,1179],{"class":121},[98,1260,1262,1264,1266,1268,1270,1273],{"class":100,"line":1261},19,[98,1263,854],{"class":121},[98,1265,857],{"class":128},[98,1267,1026],{"class":121},[98,1269,1190],{"class":128},[98,1271,1272],{"class":121},"(wrapped2))  ",[98,1274,1275],{"class":104},"\u002F\u002F \"connection refused\"\n",[143,1277,1278,1280,1281,1283,1284,1287,1288,1291,1292,1294,1295,1297],{},[16,1279,1068],{}," создаёт строку без метода ",[16,1282,1190],{}," — ",[16,1285,1286],{},"errors.Unwrap"," возвращает ",[16,1289,1290],{},"nil",". ",[16,1293,1011],{}," создаёт специальный тип с методом ",[16,1296,1081],{},", который возвращает оригинальную ошибку.",[84,1299,1301],{"id":1300},"цепочка-оборачивания","Цепочка оборачивания",[89,1303,1305],{"className":91,"code":1304,"language":93,"meta":94,"style":94},"err1 := errors.New(\"disk full\")\nerr2 := fmt.Errorf(\"write failed: %w\", err1)\nerr3 := fmt.Errorf(\"save config: %w\", err2)\n\n\u002F\u002F Разворачиваем по одному:\nfmt.Println(err3)                      \u002F\u002F \"save config: write failed: disk full\"\nfmt.Println(errors.Unwrap(err3))       \u002F\u002F \"write failed: disk full\"\nfmt.Println(errors.Unwrap(errors.Unwrap(err3)))  \u002F\u002F \"disk full\"\nfmt.Println(errors.Unwrap(errors.Unwrap(errors.Unwrap(err3))))  \u002F\u002F \u003Cnil>\n",[16,1306,1307,1325,1348,1371,1375,1380,1392,1408,1428],{"__ignoreMap":94},[98,1308,1309,1312,1314,1316,1318,1320,1323],{"class":100,"line":101},[98,1310,1311],{"class":121},"err1 ",[98,1313,170],{"class":111},[98,1315,173],{"class":121},[98,1317,176],{"class":128},[98,1319,179],{"class":121},[98,1321,1322],{"class":182},"\"disk full\"",[98,1324,186],{"class":121},[98,1326,1327,1330,1332,1334,1336,1338,1341,1343,1345],{"class":100,"line":108},[98,1328,1329],{"class":121},"err2 ",[98,1331,170],{"class":111},[98,1333,207],{"class":121},[98,1335,210],{"class":128},[98,1337,179],{"class":121},[98,1339,1340],{"class":182},"\"write failed: ",[98,1342,1011],{"class":218},[98,1344,1014],{"class":182},[98,1346,1347],{"class":121},", err1)\n",[98,1349,1350,1353,1355,1357,1359,1361,1364,1366,1368],{"class":100,"line":125},[98,1351,1352],{"class":121},"err3 ",[98,1354,170],{"class":111},[98,1356,207],{"class":121},[98,1358,210],{"class":128},[98,1360,179],{"class":121},[98,1362,1363],{"class":182},"\"save config: ",[98,1365,1011],{"class":218},[98,1367,1014],{"class":182},[98,1369,1370],{"class":121},", err2)\n",[98,1372,1373],{"class":100,"line":138},[98,1374,192],{"emptyLinePlaceholder":191},[98,1376,1377],{"class":100,"line":200},[98,1378,1379],{"class":104},"\u002F\u002F Разворачиваем по одному:\n",[98,1381,1382,1384,1386,1389],{"class":100,"line":304},[98,1383,854],{"class":121},[98,1385,857],{"class":128},[98,1387,1388],{"class":121},"(err3)                      ",[98,1390,1391],{"class":104},"\u002F\u002F \"save config: write failed: disk full\"\n",[98,1393,1394,1396,1398,1400,1402,1405],{"class":100,"line":329},[98,1395,854],{"class":121},[98,1397,857],{"class":128},[98,1399,1026],{"class":121},[98,1401,1190],{"class":128},[98,1403,1404],{"class":121},"(err3))       ",[98,1406,1407],{"class":104},"\u002F\u002F \"write failed: disk full\"\n",[98,1409,1410,1412,1414,1416,1418,1420,1422,1425],{"class":100,"line":352},[98,1411,854],{"class":121},[98,1413,857],{"class":128},[98,1415,1026],{"class":121},[98,1417,1190],{"class":128},[98,1419,1026],{"class":121},[98,1421,1190],{"class":128},[98,1423,1424],{"class":121},"(err3)))  ",[98,1426,1427],{"class":104},"\u002F\u002F \"disk full\"\n",[98,1429,1430,1432,1434,1436,1438,1440,1442,1444,1446,1449],{"class":100,"line":357},[98,1431,854],{"class":121},[98,1433,857],{"class":128},[98,1435,1026],{"class":121},[98,1437,1190],{"class":128},[98,1439,1026],{"class":121},[98,1441,1190],{"class":128},[98,1443,1026],{"class":121},[98,1445,1190],{"class":128},[98,1447,1448],{"class":121},"(err3))))  ",[98,1450,1451],{"class":104},"\u002F\u002F \u003Cnil>\n",[143,1453,1454,1455,1457,1458,1460],{},"Каждый ",[16,1456,1011],{}," добавляет слой. Unwrap снимает по одному. Финальный Unwrap на самой глубокой ошибке возвращает ",[16,1459,1290],{},".",[84,1462,1464],{"id":1463},"два-сценария-использования","Два сценария использования",[89,1466,1468],{"className":91,"code":1467,"language":93,"meta":94,"style":94},"\u002F\u002F 1. Добавить контекст при пробросе\nfunc ReadConfig(path string) error {\n    data, err := os.ReadFile(path)\n    if err != nil {\n        return fmt.Errorf(\"reading config %s: %w\", path, err)\n    }\n    \u002F\u002F ...\n}\n\n\u002F\u002F 2. Пометить sentinel'ом\nvar ErrValidation = errors.New(\"validation error\")\n\nfunc Validate(input string) error {\n    if input == \"\" {\n        return fmt.Errorf(\"%w: empty input\", ErrValidation)\n    }\n    \u002F\u002F ...\n}\n",[16,1469,1470,1475,1494,1510,1522,1548,1552,1557,1561,1565,1570,1590,1594,1614,1628,1648,1652,1656],{"__ignoreMap":94},[98,1471,1472],{"class":100,"line":101},[98,1473,1474],{"class":104},"\u002F\u002F 1. Добавить контекст при пробросе\n",[98,1476,1477,1479,1482,1484,1486,1488,1490,1492],{"class":100,"line":108},[98,1478,414],{"class":111},[98,1480,1481],{"class":128}," ReadConfig",[98,1483,179],{"class":121},[98,1485,423],{"class":422},[98,1487,426],{"class":111},[98,1489,289],{"class":121},[98,1491,18],{"class":111},[98,1493,122],{"class":121},[98,1495,1496,1499,1501,1504,1507],{"class":100,"line":125},[98,1497,1498],{"class":121},"    data, err ",[98,1500,170],{"class":111},[98,1502,1503],{"class":121}," os.",[98,1505,1506],{"class":128},"ReadFile",[98,1508,1509],{"class":121},"(path)\n",[98,1511,1512,1514,1516,1518,1520],{"class":100,"line":138},[98,1513,526],{"class":111},[98,1515,529],{"class":121},[98,1517,532],{"class":111},[98,1519,535],{"class":218},[98,1521,122],{"class":121},[98,1523,1524,1526,1528,1530,1532,1535,1538,1541,1543,1545],{"class":100,"line":200},[98,1525,542],{"class":111},[98,1527,207],{"class":121},[98,1529,210],{"class":128},[98,1531,179],{"class":121},[98,1533,1534],{"class":182},"\"reading config ",[98,1536,1537],{"class":218},"%s",[98,1539,1540],{"class":182},": ",[98,1542,1011],{"class":218},[98,1544,1014],{"class":182},[98,1546,1547],{"class":121},", path, err)\n",[98,1549,1550],{"class":100,"line":304},[98,1551,550],{"class":121},[98,1553,1554],{"class":100,"line":329},[98,1555,1556],{"class":104},"    \u002F\u002F ...\n",[98,1558,1559],{"class":100,"line":352},[98,1560,141],{"class":121},[98,1562,1563],{"class":100,"line":357},[98,1564,192],{"emptyLinePlaceholder":191},[98,1566,1567],{"class":100,"line":363},[98,1568,1569],{"class":104},"\u002F\u002F 2. Пометить sentinel'ом\n",[98,1571,1572,1574,1577,1579,1581,1583,1585,1588],{"class":100,"line":381},[98,1573,247],{"class":111},[98,1575,1576],{"class":121}," ErrValidation ",[98,1578,253],{"class":111},[98,1580,173],{"class":121},[98,1582,176],{"class":128},[98,1584,179],{"class":121},[98,1586,1587],{"class":182},"\"validation error\"",[98,1589,186],{"class":121},[98,1591,1592],{"class":100,"line":586},[98,1593,192],{"emptyLinePlaceholder":191},[98,1595,1596,1598,1601,1603,1606,1608,1610,1612],{"class":100,"line":595},[98,1597,414],{"class":111},[98,1599,1600],{"class":128}," Validate",[98,1602,179],{"class":121},[98,1604,1605],{"class":422},"input",[98,1607,426],{"class":111},[98,1609,289],{"class":121},[98,1611,18],{"class":111},[98,1613,122],{"class":121},[98,1615,1616,1618,1621,1623,1626],{"class":100,"line":1213},[98,1617,526],{"class":111},[98,1619,1620],{"class":121}," input ",[98,1622,767],{"class":111},[98,1624,1625],{"class":182}," \"\"",[98,1627,122],{"class":121},[98,1629,1630,1632,1634,1636,1638,1640,1642,1645],{"class":100,"line":1219},[98,1631,542],{"class":111},[98,1633,207],{"class":121},[98,1635,210],{"class":128},[98,1637,179],{"class":121},[98,1639,1014],{"class":182},[98,1641,1011],{"class":218},[98,1643,1644],{"class":182},": empty input\"",[98,1646,1647],{"class":121},", ErrValidation)\n",[98,1649,1650],{"class":100,"line":1228},[98,1651,550],{"class":121},[98,1653,1654],{"class":100,"line":1234},[98,1655,1556],{"class":104},[98,1657,1658],{"class":100,"line":1239},[98,1659,141],{"class":121},[143,1661,1662,1663,1665],{},"Сценарий 1: ",[16,1664,1011],{}," добавляет контекст — теперь ошибка несёт информацию о пути и операции.",[143,1667,1668,1669,1672],{},"Сценарий 2: оборачивание sentinel'а — ",[16,1670,1671],{},"errors.Is(err, ErrValidation)"," будет работать, потому что цепочка разворачивается рекурсивно.",[84,1674,1676],{"id":1675},"ловушки","Ловушки",[89,1678,1680],{"className":91,"code":1679,"language":93,"meta":94,"style":94},"\u002F\u002F Строка вместо ошибки — unwrap не сработает как ожидалось\nwrapped := fmt.Errorf(\"error: %w\", \"not an error\")  \u002F\u002F строка, не error!\n\n\u002F\u002F Два %w — поведение может быть неочевидным\nwrapped := fmt.Errorf(\"%w and %w\", err1, err2)\n\u002F\u002F Go 1.20+ поддерживает multiple wrapping, но осторожно\n",[16,1681,1682,1687,1716,1720,1725,1751],{"__ignoreMap":94},[98,1683,1684],{"class":100,"line":101},[98,1685,1686],{"class":104},"\u002F\u002F Строка вместо ошибки — unwrap не сработает как ожидалось\n",[98,1688,1689,1691,1693,1695,1697,1699,1702,1704,1706,1708,1711,1713],{"class":100,"line":108},[98,1690,997],{"class":121},[98,1692,170],{"class":111},[98,1694,207],{"class":121},[98,1696,210],{"class":128},[98,1698,179],{"class":121},[98,1700,1701],{"class":182},"\"error: ",[98,1703,1011],{"class":218},[98,1705,1014],{"class":182},[98,1707,56],{"class":121},[98,1709,1710],{"class":182},"\"not an error\"",[98,1712,375],{"class":121},[98,1714,1715],{"class":104},"\u002F\u002F строка, не error!\n",[98,1717,1718],{"class":100,"line":125},[98,1719,192],{"emptyLinePlaceholder":191},[98,1721,1722],{"class":100,"line":138},[98,1723,1724],{"class":104},"\u002F\u002F Два %w — поведение может быть неочевидным\n",[98,1726,1727,1729,1731,1733,1735,1737,1739,1741,1744,1746,1748],{"class":100,"line":200},[98,1728,997],{"class":121},[98,1730,170],{"class":111},[98,1732,207],{"class":121},[98,1734,210],{"class":128},[98,1736,179],{"class":121},[98,1738,1014],{"class":182},[98,1740,1011],{"class":218},[98,1742,1743],{"class":182}," and ",[98,1745,1011],{"class":218},[98,1747,1014],{"class":182},[98,1749,1750],{"class":121},", err1, err2)\n",[98,1752,1753],{"class":100,"line":304},[98,1754,1755],{"class":104},"\u002F\u002F Go 1.20+ поддерживает multiple wrapping, но осторожно\n",[143,1757,1758,1759,1761,1762,1765,1766,1768],{},"Go 1.20+ поддерживает несколько ",[16,1760,1011],{}," в одном ",[16,1763,1764],{},"fmt.Errorf"," — создаётся ошибка с несколькими обёрнутыми ошибками (multiple wrapping). До 1.20 поведение с двумя ",[16,1767,1011],{}," было undefined.",[81,1770],{},[10,1772,1773,1782,1791,1797,1804],{},[13,1774,1775,1777,1778,1781],{},[16,1776,638],{}," — рекурсивный Unwrap + сравнение по ",[38,1779,1780],{},"значению"," (для sentinel)",[13,1783,1784,1777,1787,1790],{},[16,1785,1786],{},"errors.As",[38,1788,1789],{},"типу"," (для кастомных типов)",[13,1792,1793,1796],{},[38,1794,1795],{},"всегда"," использовать Is\u002FAs вместо == и type switch — код меняется, обёртки добавляются",[13,1798,1799,1800,1803],{},"type switch по обёрнутой ошибке ",[38,1801,1802],{},"не найдёт"," исходный тип (за интерфейсом будет тип обёртки)",[13,1805,1806,1807,1810],{},"специальный приём: оборачивание sentinel без контекста — чтобы ",[38,1808,1809],{},"запретить"," ==",[81,1812],{},[84,1814,1816],{"id":1815},"errorsis-сравнение-по-значению","errors.Is — сравнение по значению",[89,1818,1820],{"className":91,"code":1819,"language":93,"meta":94,"style":94},"var ErrDB = errors.New(\"database problem\")\n\nwrapped := fmt.Errorf(\"query: %w\", ErrDB)\n\n\u002F\u002F ❌ не сработает — wrapped != ErrDB (разные объекты)\nfmt.Println(wrapped == ErrDB)           \u002F\u002F false\n\n\u002F\u002F ✅ рекурсивно unwrap'ит и находит ErrDB внутри\nfmt.Println(errors.Is(wrapped, ErrDB))  \u002F\u002F true\n",[16,1821,1822,1841,1845,1867,1871,1876,1893,1897,1902],{"__ignoreMap":94},[98,1823,1824,1826,1829,1831,1833,1835,1837,1839],{"class":100,"line":101},[98,1825,247],{"class":111},[98,1827,1828],{"class":121}," ErrDB ",[98,1830,253],{"class":111},[98,1832,173],{"class":121},[98,1834,176],{"class":128},[98,1836,179],{"class":121},[98,1838,676],{"class":182},[98,1840,186],{"class":121},[98,1842,1843],{"class":100,"line":108},[98,1844,192],{"emptyLinePlaceholder":191},[98,1846,1847,1849,1851,1853,1855,1857,1860,1862,1864],{"class":100,"line":125},[98,1848,997],{"class":121},[98,1850,170],{"class":111},[98,1852,207],{"class":121},[98,1854,210],{"class":128},[98,1856,179],{"class":121},[98,1858,1859],{"class":182},"\"query: ",[98,1861,1011],{"class":218},[98,1863,1014],{"class":182},[98,1865,1866],{"class":121},", ErrDB)\n",[98,1868,1869],{"class":100,"line":138},[98,1870,192],{"emptyLinePlaceholder":191},[98,1872,1873],{"class":100,"line":200},[98,1874,1875],{"class":104},"\u002F\u002F ❌ не сработает — wrapped != ErrDB (разные объекты)\n",[98,1877,1878,1880,1882,1885,1887,1890],{"class":100,"line":304},[98,1879,854],{"class":121},[98,1881,857],{"class":128},[98,1883,1884],{"class":121},"(wrapped ",[98,1886,767],{"class":111},[98,1888,1889],{"class":121}," ErrDB)           ",[98,1891,1892],{"class":104},"\u002F\u002F false\n",[98,1894,1895],{"class":100,"line":329},[98,1896,192],{"emptyLinePlaceholder":191},[98,1898,1899],{"class":100,"line":352},[98,1900,1901],{"class":104},"\u002F\u002F ✅ рекурсивно unwrap'ит и находит ErrDB внутри\n",[98,1903,1904,1906,1908,1910,1912,1915],{"class":100,"line":357},[98,1905,854],{"class":121},[98,1907,857],{"class":128},[98,1909,1026],{"class":121},[98,1911,1029],{"class":128},[98,1913,1914],{"class":121},"(wrapped, ErrDB))  ",[98,1916,1035],{"class":104},[143,1918,1919,1921,1922,1924],{},[16,1920,638],{}," рекурсивно вызывает ",[16,1923,1081],{}," на каждом уровне цепочки, пока не найдёт совпадение по значению или не исчерпает цепочку.",[84,1926,1928],{"id":1927},"errorsas-сравнение-по-типу","errors.As — сравнение по типу",[89,1930,1932],{"className":91,"code":1931,"language":93,"meta":94,"style":94},"type DatabaseError struct {\n    Query string\n    Err   error\n}\nfunc (e *DatabaseError) Error() string { return e.Err.Error() }\n\noriginal := &DatabaseError{Query: \"SELECT\", Err: errors.New(\"timeout\")}\nwrapped := fmt.Errorf(\"handler: %w\", original)\n\n\u002F\u002F ❌ type switch не найдёт — за интерфейсом тип обёртки, не DatabaseError\nswitch wrapped.(type) {\ncase *DatabaseError:  \u002F\u002F сюда НЕ зайдёт\n}\n\n\u002F\u002F ✅ errors.As рекурсивно ищет нужный тип\nvar dbErr *DatabaseError\nif errors.As(wrapped, &dbErr) {\n    fmt.Println(dbErr.Query)  \u002F\u002F \"SELECT\"\n}\n",[16,1933,1934,1945,1952,1960,1964,1999,2003,2032,2053,2057,2062,2074,2090,2094,2098,2103,2115,2133,2146],{"__ignoreMap":94},[98,1935,1936,1938,1941,1943],{"class":100,"line":101},[98,1937,112],{"class":111},[98,1939,1940],{"class":128}," DatabaseError",[98,1942,312],{"class":111},[98,1944,122],{"class":121},[98,1946,1947,1950],{"class":100,"line":108},[98,1948,1949],{"class":121},"    Query ",[98,1951,135],{"class":111},[98,1953,1954,1957],{"class":100,"line":125},[98,1955,1956],{"class":121},"    Err   ",[98,1958,1959],{"class":111},"error\n",[98,1961,1962],{"class":100,"line":138},[98,1963,141],{"class":121},[98,1965,1966,1968,1970,1972,1974,1977,1979,1981,1983,1985,1988,1991,1994,1996],{"class":100,"line":200},[98,1967,414],{"class":111},[98,1969,52],{"class":121},[98,1971,903],{"class":422},[98,1973,707],{"class":111},[98,1975,1976],{"class":128},"DatabaseError",[98,1978,289],{"class":121},[98,1980,66],{"class":128},[98,1982,132],{"class":121},[98,1984,318],{"class":111},[98,1986,1987],{"class":121}," { ",[98,1989,1990],{"class":111},"return",[98,1992,1993],{"class":121}," e.Err.",[98,1995,66],{"class":128},[98,1997,1998],{"class":121},"() }\n",[98,2000,2001],{"class":100,"line":304},[98,2002,192],{"emptyLinePlaceholder":191},[98,2004,2005,2007,2009,2011,2013,2016,2019,2022,2024,2026,2029],{"class":100,"line":329},[98,2006,1111],{"class":121},[98,2008,170],{"class":111},[98,2010,790],{"class":111},[98,2012,1976],{"class":128},[98,2014,2015],{"class":121},"{Query: ",[98,2017,2018],{"class":182},"\"SELECT\"",[98,2020,2021],{"class":121},", Err: errors.",[98,2023,176],{"class":128},[98,2025,179],{"class":121},[98,2027,2028],{"class":182},"\"timeout\"",[98,2030,2031],{"class":121},")}\n",[98,2033,2034,2036,2038,2040,2042,2044,2047,2049,2051],{"class":100,"line":352},[98,2035,997],{"class":121},[98,2037,170],{"class":111},[98,2039,207],{"class":121},[98,2041,210],{"class":128},[98,2043,179],{"class":121},[98,2045,2046],{"class":182},"\"handler: ",[98,2048,1011],{"class":218},[98,2050,1014],{"class":182},[98,2052,1179],{"class":121},[98,2054,2055],{"class":100,"line":357},[98,2056,192],{"emptyLinePlaceholder":191},[98,2058,2059],{"class":100,"line":363},[98,2060,2061],{"class":104},"\u002F\u002F ❌ type switch не найдёт — за интерфейсом тип обёртки, не DatabaseError\n",[98,2063,2064,2067,2070,2072],{"class":100,"line":381},[98,2065,2066],{"class":111},"switch",[98,2068,2069],{"class":121}," wrapped.(",[98,2071,112],{"class":111},[98,2073,717],{"class":121},[98,2075,2076,2079,2082,2084,2087],{"class":100,"line":586},[98,2077,2078],{"class":111},"case",[98,2080,2081],{"class":111}," *",[98,2083,1976],{"class":128},[98,2085,2086],{"class":121},":  ",[98,2088,2089],{"class":104},"\u002F\u002F сюда НЕ зайдёт\n",[98,2091,2092],{"class":100,"line":595},[98,2093,141],{"class":121},[98,2095,2096],{"class":100,"line":1213},[98,2097,192],{"emptyLinePlaceholder":191},[98,2099,2100],{"class":100,"line":1219},[98,2101,2102],{"class":104},"\u002F\u002F ✅ errors.As рекурсивно ищет нужный тип\n",[98,2104,2105,2107,2110,2112],{"class":100,"line":1228},[98,2106,247],{"class":111},[98,2108,2109],{"class":121}," dbErr ",[98,2111,707],{"class":111},[98,2113,2114],{"class":128},"DatabaseError\n",[98,2116,2117,2120,2122,2125,2128,2130],{"class":100,"line":1234},[98,2118,2119],{"class":111},"if",[98,2121,173],{"class":121},[98,2123,2124],{"class":128},"As",[98,2126,2127],{"class":121},"(wrapped, ",[98,2129,755],{"class":111},[98,2131,2132],{"class":121},"dbErr) {\n",[98,2134,2135,2138,2140,2143],{"class":100,"line":1239},[98,2136,2137],{"class":121},"    fmt.",[98,2139,857],{"class":128},[98,2141,2142],{"class":121},"(dbErr.Query)  ",[98,2144,2145],{"class":104},"\u002F\u002F \"SELECT\"\n",[98,2147,2148],{"class":100,"line":1261},[98,2149,141],{"class":121},[143,2151,2152,2154],{},[16,2153,1786],{}," рекурсивно разворачивает цепочку, ища ошибку, assignable к типу целевого указателя, и присваивает её при нахождении.",[84,2156,2158],{"id":2157},"почему-всегда-isas","Почему всегда Is\u002FAs",[89,2160,2162],{"className":91,"code":2161,"language":93,"meta":94,"style":94},"\u002F\u002F Сейчас работает:\nif err == sql.ErrNoRows { ... }\n\n\u002F\u002F Завтра разработчик добавил контекст:\nreturn fmt.Errorf(\"users table: %w\", sql.ErrNoRows)\n\n\u002F\u002F И ваш == сломался! А errors.Is продолжит работать:\nif errors.Is(err, sql.ErrNoRows) { ... }  \u002F\u002F ✅ всегда\n",[16,2163,2164,2169,2184,2188,2193,2213,2217,2222],{"__ignoreMap":94},[98,2165,2166],{"class":100,"line":101},[98,2167,2168],{"class":104},"\u002F\u002F Сейчас работает:\n",[98,2170,2171,2173,2175,2177,2180,2182],{"class":100,"line":108},[98,2172,2119],{"class":111},[98,2174,529],{"class":121},[98,2176,767],{"class":111},[98,2178,2179],{"class":121}," sql.ErrNoRows { ",[98,2181,442],{"class":111},[98,2183,326],{"class":121},[98,2185,2186],{"class":100,"line":125},[98,2187,192],{"emptyLinePlaceholder":191},[98,2189,2190],{"class":100,"line":138},[98,2191,2192],{"class":104},"\u002F\u002F Завтра разработчик добавил контекст:\n",[98,2194,2195,2197,2199,2201,2203,2206,2208,2210],{"class":100,"line":200},[98,2196,1990],{"class":111},[98,2198,207],{"class":121},[98,2200,210],{"class":128},[98,2202,179],{"class":121},[98,2204,2205],{"class":182},"\"users table: ",[98,2207,1011],{"class":218},[98,2209,1014],{"class":182},[98,2211,2212],{"class":121},", sql.ErrNoRows)\n",[98,2214,2215],{"class":100,"line":304},[98,2216,192],{"emptyLinePlaceholder":191},[98,2218,2219],{"class":100,"line":329},[98,2220,2221],{"class":104},"\u002F\u002F И ваш == сломался! А errors.Is продолжит работать:\n",[98,2223,2224,2226,2228,2230,2233,2235,2238],{"class":100,"line":352},[98,2225,2119],{"class":111},[98,2227,173],{"class":121},[98,2229,1029],{"class":128},[98,2231,2232],{"class":121},"(err, sql.ErrNoRows) { ",[98,2234,442],{"class":111},[98,2236,2237],{"class":121}," }  ",[98,2239,2240],{"class":104},"\u002F\u002F ✅ всегда\n",[143,2242,2243],{},"Код меняется — обёртки добавляются. Is\u002FAs защищают от этого.",[84,2245,2247],{"id":2246},"запрет-сравнения-через","Запрет сравнения через ==",[89,2249,2251],{"className":91,"code":2250,"language":93,"meta":94,"style":94},"\u002F\u002F Специально оборачивают без контекста:\nvar errBase = errors.New(\"access denied\")\nvar ErrAccessDenied = fmt.Errorf(\"%w\", errBase)\n\n\u002F\u002F Теперь == не сработает НИКОГДА — только errors.Is\nfmt.Println(err == ErrAccessDenied)           \u002F\u002F false\nfmt.Println(errors.Is(err, ErrAccessDenied))  \u002F\u002F true\n",[16,2252,2253,2258,2278,2302,2306,2311,2327],{"__ignoreMap":94},[98,2254,2255],{"class":100,"line":101},[98,2256,2257],{"class":104},"\u002F\u002F Специально оборачивают без контекста:\n",[98,2259,2260,2262,2265,2267,2269,2271,2273,2276],{"class":100,"line":108},[98,2261,247],{"class":111},[98,2263,2264],{"class":121}," errBase ",[98,2266,253],{"class":111},[98,2268,173],{"class":121},[98,2270,176],{"class":128},[98,2272,179],{"class":121},[98,2274,2275],{"class":182},"\"access denied\"",[98,2277,186],{"class":121},[98,2279,2280,2282,2285,2287,2289,2291,2293,2295,2297,2299],{"class":100,"line":125},[98,2281,247],{"class":111},[98,2283,2284],{"class":121}," ErrAccessDenied ",[98,2286,253],{"class":111},[98,2288,207],{"class":121},[98,2290,210],{"class":128},[98,2292,179],{"class":121},[98,2294,1014],{"class":182},[98,2296,1011],{"class":218},[98,2298,1014],{"class":182},[98,2300,2301],{"class":121},", errBase)\n",[98,2303,2304],{"class":100,"line":138},[98,2305,192],{"emptyLinePlaceholder":191},[98,2307,2308],{"class":100,"line":200},[98,2309,2310],{"class":104},"\u002F\u002F Теперь == не сработает НИКОГДА — только errors.Is\n",[98,2312,2313,2315,2317,2320,2322,2325],{"class":100,"line":304},[98,2314,854],{"class":121},[98,2316,857],{"class":128},[98,2318,2319],{"class":121},"(err ",[98,2321,767],{"class":111},[98,2323,2324],{"class":121}," ErrAccessDenied)           ",[98,2326,1892],{"class":104},[98,2328,2329,2331,2333,2335,2337,2340],{"class":100,"line":329},[98,2330,854],{"class":121},[98,2332,857],{"class":128},[98,2334,1026],{"class":121},[98,2336,1029],{"class":128},[98,2338,2339],{"class":121},"(err, ErrAccessDenied))  ",[98,2341,1035],{"class":104},[143,2343,2344],{},"Приём для библиотек: заставить пользователей сразу использовать errors.Is.",[84,2346,2348],{"id":2347},"никогда-не-сравнивай-текст-ошибки","Никогда не сравнивай текст ошибки",[89,2350,2352],{"className":91,"code":2351,"language":93,"meta":94,"style":94},"\u002F\u002F ❌ хрупко, антипаттерн\nif err.Error() == \"connection refused\" { ... }\n",[16,2353,2354,2359],{"__ignoreMap":94},[98,2355,2356],{"class":100,"line":101},[98,2357,2358],{"class":104},"\u002F\u002F ❌ хрупко, антипаттерн\n",[98,2360,2361,2363,2366,2368,2370,2372,2375,2377,2379],{"class":100,"line":108},[98,2362,2119],{"class":111},[98,2364,2365],{"class":121}," err.",[98,2367,66],{"class":128},[98,2369,132],{"class":121},[98,2371,767],{"class":111},[98,2373,2374],{"class":182}," \"connection refused\"",[98,2376,1987],{"class":121},[98,2378,442],{"class":111},[98,2380,326],{"class":121},[143,2382,2383],{},"Текст ошибки — для людей (логи, экран), не для кода.",[81,2385],{},[10,2387,2388,2395,2402,2409,2416],{},[13,2389,2390,2391,2394],{},"ошибка должна ",[38,2392,2393],{},"рассказывать историю"," — 4 правила текста",[13,2396,2397,2398,2401],{},"обрабатывать ошибку ровно ",[38,2399,2400],{},"один раз"," (логирование = обработка)",[13,2403,2404,2405,2408],{},"не хватает контекста\u002Fответственности → ",[38,2406,2407],{},"пробрасывай"," наверх (с контекстом или без)",[13,2410,2411,2412,2415],{},"есть контекст → ",[38,2413,2414],{},"обрабатывай"," прямо здесь (лог, retry, переподключение и т.д.)",[13,2417,2418],{},"не добавляй бессмысленный контекст — если нечего сказать, просто проксируй",[81,2420],{},[84,2422,2424],{"id":2423},"_4-правила-текста-ошибки","4 правила текста ошибки",[2426,2427,2428,2438,2444,2450],"ol",{},[13,2429,2430,2433,2434,2437],{},[38,2431,2432],{},"Максимально подробно"," — не просто ",[16,2435,2436],{},"\"error\"",", а что именно случилось",[13,2439,2440,2443],{},[38,2441,2442],{},"Весь контекст для расследования"," — получая текст, можно понять причину",[13,2445,2446,2449],{},[38,2447,2448],{},"Однозначно характеризует место"," — одна и та же ошибка не из двух мест кода",[13,2451,2452,2455],{},[38,2453,2454],{},"Не слишком большой"," — логи стоят дорого, не злоупотреблять длиной",[84,2457,2459],{"id":2458},"обрабатывать-один-раз","Обрабатывать один раз",[89,2461,2463],{"className":91,"code":2462,"language":93,"meta":94,"style":94},"\u002F\u002F ❌ двойная обработка — залогировали И пробросили\nfunc GetRoute(lat, lon float64) error {\n    err := validateCoords(lat, lon)\n    if err != nil {\n        log.Printf(\"validation failed: %v\", err)       \u002F\u002F обработка 1\n        return fmt.Errorf(\"invalid coords: %w\", err)    \u002F\u002F обработка 2\n    }\n    return nil\n}\n\n\u002F\u002F ✅ либо логируем здесь (если хватает контекста)\nfunc GetRoute(lat, lon float64) error {\n    err := validateCoords(lat, lon)\n    if err != nil {\n        log.Printf(\"GetRoute: validation failed: %v\", err)\n        return err  \u002F\u002F не добавляем контекст, уже залогировали\n    }\n    return nil\n}\n\n\u002F\u002F ✅ либо пробрасываем с контекстом (если не хватает)\nfunc GetRoute(lat, lon float64) error {\n    err := validateCoords(lat, lon)\n    if err != nil {\n        return fmt.Errorf(\"validate coords in GetRoute: %w\", err)\n    }\n    return nil\n}\n",[16,2464,2465,2470,2496,2508,2520,2543,2566,2570,2576,2580,2584,2589,2611,2621,2633,2651,2661,2665,2671,2675,2680,2686,2709,2720,2733,2753,2758,2765],{"__ignoreMap":94},[98,2466,2467],{"class":100,"line":101},[98,2468,2469],{"class":104},"\u002F\u002F ❌ двойная обработка — залогировали И пробросили\n",[98,2471,2472,2474,2477,2479,2482,2484,2487,2490,2492,2494],{"class":100,"line":108},[98,2473,414],{"class":111},[98,2475,2476],{"class":128}," GetRoute",[98,2478,179],{"class":121},[98,2480,2481],{"class":422},"lat",[98,2483,56],{"class":121},[98,2485,2486],{"class":422},"lon",[98,2488,2489],{"class":111}," float64",[98,2491,289],{"class":121},[98,2493,18],{"class":111},[98,2495,122],{"class":121},[98,2497,2498,2500,2502,2505],{"class":100,"line":125},[98,2499,513],{"class":121},[98,2501,170],{"class":111},[98,2503,2504],{"class":128}," validateCoords",[98,2506,2507],{"class":121},"(lat, lon)\n",[98,2509,2510,2512,2514,2516,2518],{"class":100,"line":138},[98,2511,526],{"class":111},[98,2513,529],{"class":121},[98,2515,532],{"class":111},[98,2517,535],{"class":218},[98,2519,122],{"class":121},[98,2521,2522,2525,2528,2530,2533,2535,2537,2540],{"class":100,"line":200},[98,2523,2524],{"class":121},"        log.",[98,2526,2527],{"class":128},"Printf",[98,2529,179],{"class":121},[98,2531,2532],{"class":182},"\"validation failed: ",[98,2534,1068],{"class":218},[98,2536,1014],{"class":182},[98,2538,2539],{"class":121},", err)       ",[98,2541,2542],{"class":104},"\u002F\u002F обработка 1\n",[98,2544,2545,2547,2549,2551,2553,2556,2558,2560,2563],{"class":100,"line":304},[98,2546,542],{"class":111},[98,2548,207],{"class":121},[98,2550,210],{"class":128},[98,2552,179],{"class":121},[98,2554,2555],{"class":182},"\"invalid coords: ",[98,2557,1011],{"class":218},[98,2559,1014],{"class":182},[98,2561,2562],{"class":121},", err)    ",[98,2564,2565],{"class":104},"\u002F\u002F обработка 2\n",[98,2567,2568],{"class":100,"line":329},[98,2569,550],{"class":121},[98,2571,2572,2574],{"class":100,"line":352},[98,2573,555],{"class":111},[98,2575,558],{"class":218},[98,2577,2578],{"class":100,"line":357},[98,2579,141],{"class":121},[98,2581,2582],{"class":100,"line":363},[98,2583,192],{"emptyLinePlaceholder":191},[98,2585,2586],{"class":100,"line":381},[98,2587,2588],{"class":104},"\u002F\u002F ✅ либо логируем здесь (если хватает контекста)\n",[98,2590,2591,2593,2595,2597,2599,2601,2603,2605,2607,2609],{"class":100,"line":586},[98,2592,414],{"class":111},[98,2594,2476],{"class":128},[98,2596,179],{"class":121},[98,2598,2481],{"class":422},[98,2600,56],{"class":121},[98,2602,2486],{"class":422},[98,2604,2489],{"class":111},[98,2606,289],{"class":121},[98,2608,18],{"class":111},[98,2610,122],{"class":121},[98,2612,2613,2615,2617,2619],{"class":100,"line":595},[98,2614,513],{"class":121},[98,2616,170],{"class":111},[98,2618,2504],{"class":128},[98,2620,2507],{"class":121},[98,2622,2623,2625,2627,2629,2631],{"class":100,"line":1213},[98,2624,526],{"class":111},[98,2626,529],{"class":121},[98,2628,532],{"class":111},[98,2630,535],{"class":218},[98,2632,122],{"class":121},[98,2634,2635,2637,2639,2641,2644,2646,2648],{"class":100,"line":1219},[98,2636,2524],{"class":121},[98,2638,2527],{"class":128},[98,2640,179],{"class":121},[98,2642,2643],{"class":182},"\"GetRoute: validation failed: ",[98,2645,1068],{"class":218},[98,2647,1014],{"class":182},[98,2649,2650],{"class":121},", err)\n",[98,2652,2653,2655,2658],{"class":100,"line":1228},[98,2654,542],{"class":111},[98,2656,2657],{"class":121}," err  ",[98,2659,2660],{"class":104},"\u002F\u002F не добавляем контекст, уже залогировали\n",[98,2662,2663],{"class":100,"line":1234},[98,2664,550],{"class":121},[98,2666,2667,2669],{"class":100,"line":1239},[98,2668,555],{"class":111},[98,2670,558],{"class":218},[98,2672,2673],{"class":100,"line":1261},[98,2674,141],{"class":121},[98,2676,2678],{"class":100,"line":2677},20,[98,2679,192],{"emptyLinePlaceholder":191},[98,2681,2683],{"class":100,"line":2682},21,[98,2684,2685],{"class":104},"\u002F\u002F ✅ либо пробрасываем с контекстом (если не хватает)\n",[98,2687,2689,2691,2693,2695,2697,2699,2701,2703,2705,2707],{"class":100,"line":2688},22,[98,2690,414],{"class":111},[98,2692,2476],{"class":128},[98,2694,179],{"class":121},[98,2696,2481],{"class":422},[98,2698,56],{"class":121},[98,2700,2486],{"class":422},[98,2702,2489],{"class":111},[98,2704,289],{"class":121},[98,2706,18],{"class":111},[98,2708,122],{"class":121},[98,2710,2712,2714,2716,2718],{"class":100,"line":2711},23,[98,2713,513],{"class":121},[98,2715,170],{"class":111},[98,2717,2504],{"class":128},[98,2719,2507],{"class":121},[98,2721,2723,2725,2727,2729,2731],{"class":100,"line":2722},24,[98,2724,526],{"class":111},[98,2726,529],{"class":121},[98,2728,532],{"class":111},[98,2730,535],{"class":218},[98,2732,122],{"class":121},[98,2734,2736,2738,2740,2742,2744,2747,2749,2751],{"class":100,"line":2735},25,[98,2737,542],{"class":111},[98,2739,207],{"class":121},[98,2741,210],{"class":128},[98,2743,179],{"class":121},[98,2745,2746],{"class":182},"\"validate coords in GetRoute: ",[98,2748,1011],{"class":218},[98,2750,1014],{"class":182},[98,2752,2650],{"class":121},[98,2754,2756],{"class":100,"line":2755},26,[98,2757,550],{"class":121},[98,2759,2761,2763],{"class":100,"line":2760},27,[98,2762,555],{"class":111},[98,2764,558],{"class":218},[98,2766,2768],{"class":100,"line":2767},28,[98,2769,141],{"class":121},[84,2771,2773],{"id":2772},"проброс-не-обработка","Проброс — не обработка",[143,2775,2776,2777,2779,2780,2783],{},"Проброс ошибки наверх (с контекстом или без) — это ",[38,2778,641],{}," обработка. Обработка — это когда вы что-то ",[38,2781,2782],{},"делаете",": логируете, делаете retry, отвечаете клиенту, меняете поведение.",[84,2785,2787],{"id":2786},"не-добавляй-пустой-контекст","Не добавляй пустой контекст",[89,2789,2791],{"className":91,"code":2790,"language":93,"meta":94,"style":94},"\u002F\u002F ❌ контекст бессмысленный\nreturn fmt.Errorf(\"error: %w\", err)\n\n\u002F\u002F ✅ просто проксируй\nreturn err\n\n\u002F\u002F ✅ или добавь полезный контекст\nreturn fmt.Errorf(\"reading config file %s: %w\", path, err)\n",[16,2792,2793,2798,2816,2820,2825,2831,2835,2840],{"__ignoreMap":94},[98,2794,2795],{"class":100,"line":101},[98,2796,2797],{"class":104},"\u002F\u002F ❌ контекст бессмысленный\n",[98,2799,2800,2802,2804,2806,2808,2810,2812,2814],{"class":100,"line":108},[98,2801,1990],{"class":111},[98,2803,207],{"class":121},[98,2805,210],{"class":128},[98,2807,179],{"class":121},[98,2809,1701],{"class":182},[98,2811,1011],{"class":218},[98,2813,1014],{"class":182},[98,2815,2650],{"class":121},[98,2817,2818],{"class":100,"line":125},[98,2819,192],{"emptyLinePlaceholder":191},[98,2821,2822],{"class":100,"line":138},[98,2823,2824],{"class":104},"\u002F\u002F ✅ просто проксируй\n",[98,2826,2827,2829],{"class":100,"line":200},[98,2828,1990],{"class":111},[98,2830,545],{"class":121},[98,2832,2833],{"class":100,"line":304},[98,2834,192],{"emptyLinePlaceholder":191},[98,2836,2837],{"class":100,"line":329},[98,2838,2839],{"class":104},"\u002F\u002F ✅ или добавь полезный контекст\n",[98,2841,2842,2844,2846,2848,2850,2853,2855,2857,2859,2861],{"class":100,"line":352},[98,2843,1990],{"class":111},[98,2845,207],{"class":121},[98,2847,210],{"class":128},[98,2849,179],{"class":121},[98,2851,2852],{"class":182},"\"reading config file ",[98,2854,1537],{"class":218},[98,2856,1540],{"class":182},[98,2858,1011],{"class":218},[98,2860,1014],{"class":182},[98,2862,1547],{"class":121},[81,2864],{},[10,2866,2867,2874,2884,2887,2897],{},[13,2868,2869,2870,2873],{},"Go ",[38,2871,2872],{},"не поддерживает"," исключения (exceptions) — вместо них явная обработка ошибок",[13,2875,2876,2879,2880,2883],{},[16,2877,2878],{},"panic"," — механизм для ",[38,2881,2882],{},"исключительных"," ситуаций, когда продолжение невозможно",[13,2885,2886],{},"panic ≠ throw: throw делает проблему вызывающей стороны, panic = «не знаю что делать, сдаюсь»",[13,2888,2889,2892,2893,2896],{},[16,2890,2891],{},"recover"," работает ",[38,2894,2895],{},"только в defer"," — вне defer возвращает nil",[13,2898,2899,2900,2903],{},"panic принимает ",[16,2901,2902],{},"any"," — можно передать строку, ошибку, что угодно, даже nil",[81,2905],{},[84,2907,2909],{"id":2908},"panic-recover","Panic + recover",[89,2911,2913],{"className":91,"code":2912,"language":93,"meta":94,"style":94},"func main() {\n    defer func() {\n        if r := recover(); r != nil {\n            fmt.Println(\"recovered:\", r)\\n        }\n    }()\n\n    fmt.Println(\"start\")\\n    panic(\"something terrible\")\n    fmt.Println(\"unreachable\")  \u002F\u002F никогда не выполнится\n}\n\u002F\u002F start\n\u002F\u002F recovered: something terrible\n",[16,2914,2915,2925,2935,2957,2972,2977,2981,3004,3020,3024,3029],{"__ignoreMap":94},[98,2916,2917,2919,2922],{"class":100,"line":101},[98,2918,414],{"class":111},[98,2920,2921],{"class":128}," main",[98,2923,2924],{"class":121},"() {\n",[98,2926,2927,2930,2933],{"class":100,"line":108},[98,2928,2929],{"class":111},"    defer",[98,2931,2932],{"class":111}," func",[98,2934,2924],{"class":121},[98,2936,2937,2940,2943,2945,2948,2951,2953,2955],{"class":100,"line":125},[98,2938,2939],{"class":111},"        if",[98,2941,2942],{"class":121}," r ",[98,2944,170],{"class":111},[98,2946,2947],{"class":128}," recover",[98,2949,2950],{"class":121},"(); r ",[98,2952,532],{"class":111},[98,2954,535],{"class":218},[98,2956,122],{"class":121},[98,2958,2959,2962,2964,2966,2969],{"class":100,"line":138},[98,2960,2961],{"class":121},"            fmt.",[98,2963,857],{"class":128},[98,2965,179],{"class":121},[98,2967,2968],{"class":182},"\"recovered:\"",[98,2970,2971],{"class":121},", r)\\n        }\n",[98,2973,2974],{"class":100,"line":200},[98,2975,2976],{"class":121},"    }()\n",[98,2978,2979],{"class":100,"line":304},[98,2980,192],{"emptyLinePlaceholder":191},[98,2982,2983,2985,2987,2989,2992,2995,2997,2999,3002],{"class":100,"line":329},[98,2984,2137],{"class":121},[98,2986,857],{"class":128},[98,2988,179],{"class":121},[98,2990,2991],{"class":182},"\"start\"",[98,2993,2994],{"class":121},")\\n    ",[98,2996,2878],{"class":128},[98,2998,179],{"class":121},[98,3000,3001],{"class":182},"\"something terrible\"",[98,3003,186],{"class":121},[98,3005,3006,3008,3010,3012,3015,3017],{"class":100,"line":352},[98,3007,2137],{"class":121},[98,3009,857],{"class":128},[98,3011,179],{"class":121},[98,3013,3014],{"class":182},"\"unreachable\"",[98,3016,375],{"class":121},[98,3018,3019],{"class":104},"\u002F\u002F никогда не выполнится\n",[98,3021,3022],{"class":100,"line":357},[98,3023,141],{"class":121},[98,3025,3026],{"class":100,"line":363},[98,3027,3028],{"class":104},"\u002F\u002F start\n",[98,3030,3031],{"class":100,"line":381},[98,3032,3033],{"class":104},"\u002F\u002F recovered: something terrible\n",[84,3035,3037],{"id":3036},"stack-unwinding-раскрутка-стека","Stack unwinding (раскрутка стека)",[89,3039,3044],{"className":3040,"code":3042,"language":3043},[3041],"language-text","1. panic() → остановка текущей функции\n2. вызов defer'ов текущей функции (LIFO)\n3. выход → вызов defer'ов вызывающей функции\n4. ... и так вверх по стеку\n5. Если нашли recover в defer → восстановление\n6. Если не нашли → завершение программы с трейсом\n","text",[16,3045,3042],{"__ignoreMap":94},[84,3047,3049],{"id":3048},"recover-только-в-defer","Recover только в defer",[89,3051,3053],{"className":91,"code":3052,"language":93,"meta":94,"style":94},"func process() {\n    recover()                    \u002F\u002F ❌ бесполезно — вернёт nil\n    defer recover()              \u002F\u002F ❌ тоже не сработает (не вызов в defer-функции)\n    defer func() { recover() }() \u002F\u002F ✅ работает\n    panic(\"error\")\n}\n",[16,3054,3055,3064,3075,3087,3104,3115],{"__ignoreMap":94},[98,3056,3057,3059,3062],{"class":100,"line":101},[98,3058,414],{"class":111},[98,3060,3061],{"class":128}," process",[98,3063,2924],{"class":121},[98,3065,3066,3069,3072],{"class":100,"line":108},[98,3067,3068],{"class":128},"    recover",[98,3070,3071],{"class":121},"()                    ",[98,3073,3074],{"class":104},"\u002F\u002F ❌ бесполезно — вернёт nil\n",[98,3076,3077,3079,3081,3084],{"class":100,"line":125},[98,3078,2929],{"class":111},[98,3080,2947],{"class":128},[98,3082,3083],{"class":121},"()              ",[98,3085,3086],{"class":104},"\u002F\u002F ❌ тоже не сработает (не вызов в defer-функции)\n",[98,3088,3089,3091,3093,3096,3098,3101],{"class":100,"line":138},[98,3090,2929],{"class":111},[98,3092,2932],{"class":111},[98,3094,3095],{"class":121},"() { ",[98,3097,2891],{"class":128},[98,3099,3100],{"class":121},"() }() ",[98,3102,3103],{"class":104},"\u002F\u002F ✅ работает\n",[98,3105,3106,3109,3111,3113],{"class":100,"line":200},[98,3107,3108],{"class":128},"    panic",[98,3110,179],{"class":121},[98,3112,2436],{"class":182},[98,3114,186],{"class":121},[98,3116,3117],{"class":100,"line":304},[98,3118,141],{"class":121},[143,3120,3121,3124,3125,1460],{},[16,3122,3123],{},"defer recover()"," не работает потому что recover вызывается напрямую как аргумент defer, а не внутри анонимной функции — он исполняется сразу, не во время паники. Правильно: ",[16,3126,3127],{},"defer func() { recover() }()",[84,3129,3131],{"id":3130},"panic-принимает-any","Panic принимает any",[89,3133,3135],{"className":91,"code":3134,"language":93,"meta":94,"style":94},"panic(\"string error\")          \u002F\u002F строка\npanic(errors.New(\"real error\")) \u002F\u002F error\npanic(42)                      \u002F\u002F int\npanic(nil)                     \u002F\u002F nil → Go 1.21+ создаёт runtime.PanicNilError\n",[16,3136,3137,3152,3171,3186],{"__ignoreMap":94},[98,3138,3139,3141,3143,3146,3149],{"class":100,"line":101},[98,3140,2878],{"class":128},[98,3142,179],{"class":121},[98,3144,3145],{"class":182},"\"string error\"",[98,3147,3148],{"class":121},")          ",[98,3150,3151],{"class":104},"\u002F\u002F строка\n",[98,3153,3154,3156,3158,3160,3162,3165,3168],{"class":100,"line":108},[98,3155,2878],{"class":128},[98,3157,1026],{"class":121},[98,3159,176],{"class":128},[98,3161,179],{"class":121},[98,3163,3164],{"class":182},"\"real error\"",[98,3166,3167],{"class":121},")) ",[98,3169,3170],{"class":104},"\u002F\u002F error\n",[98,3172,3173,3175,3177,3180,3183],{"class":100,"line":125},[98,3174,2878],{"class":128},[98,3176,179],{"class":121},[98,3178,3179],{"class":218},"42",[98,3181,3182],{"class":121},")                      ",[98,3184,3185],{"class":104},"\u002F\u002F int\n",[98,3187,3188,3190,3192,3194,3197],{"class":100,"line":138},[98,3189,2878],{"class":128},[98,3191,179],{"class":121},[98,3193,1290],{"class":218},[98,3195,3196],{"class":121},")                     ",[98,3198,3199],{"class":104},"\u002F\u002F nil → Go 1.21+ создаёт runtime.PanicNilError\n",[84,3201,3203],{"id":3202},"когда-использовать-panic","Когда использовать panic",[143,3205,3206,3207,3210,3211,1460],{},"Ошибка программиста (пришло значение, которое ",[38,3208,3209],{},"никогда"," не должно было прийти). Зависимость не инициализировалась (база не пингуется, без неё приложение бессмысленно). Исключительная ситуация, когда ",[38,3212,3213],{},"нечего обрабатывать",[84,3215,3217],{"id":3216},"почему-go-без-исключений","Почему Go без исключений",[143,3219,3220],{},"Исключения = сложность (гарантии безопасности, утечки ресурсов в C++, неявность потока управления). Go позиционируется как простой язык — явная обработка ошибок проще для понимания.",[81,3222],{},[10,3224,3225,3231,3237,3244,3250],{},[13,3226,3227,3230],{},[38,3228,3229],{},"sentinel"," — когда нужна маркировка без дополнительного контекста (io.EOF, sql.ErrNoRows)",[13,3232,3233,3236],{},[38,3234,3235],{},"кастомный тип"," — когда нужен контекст: поля с параметрами (os.PathError: Op, Path, Err)",[13,3238,3239,3240,3243],{},"оба создают ",[38,3241,3242],{},"зависимость"," между пакетами (нужен импорт для сравнения)",[13,3245,3246,3249],{},[38,3247,3248],{},"проверка по поведению"," — type assertion к анонимному интерфейсу, разрывает зависимость",[13,3251,3252,3253,3256],{},"ошибки = ",[38,3254,3255],{},"часть публичного API"," пакета, относиться бережно",[81,3258],{},[84,3260,3262],{"id":3261},"sentinel-маркировка","Sentinel — маркировка",[89,3264,3266],{"className":91,"code":3265,"language":93,"meta":94,"style":94},"var ErrNotFound = errors.New(\"not found\")\nvar ErrTimeout  = errors.New(\"timeout\")\n\n\u002F\u002F Проверка:\nif errors.Is(err, ErrNotFound) { ... }\n",[16,3267,3268,3286,3305,3309,3314],{"__ignoreMap":94},[98,3269,3270,3272,3274,3276,3278,3280,3282,3284],{"class":100,"line":101},[98,3271,247],{"class":111},[98,3273,250],{"class":121},[98,3275,253],{"class":111},[98,3277,173],{"class":121},[98,3279,176],{"class":128},[98,3281,179],{"class":121},[98,3283,262],{"class":182},[98,3285,186],{"class":121},[98,3287,3288,3290,3293,3295,3297,3299,3301,3303],{"class":100,"line":108},[98,3289,247],{"class":111},[98,3291,3292],{"class":121}," ErrTimeout  ",[98,3294,253],{"class":111},[98,3296,173],{"class":121},[98,3298,176],{"class":128},[98,3300,179],{"class":121},[98,3302,2028],{"class":182},[98,3304,186],{"class":121},[98,3306,3307],{"class":100,"line":125},[98,3308,192],{"emptyLinePlaceholder":191},[98,3310,3311],{"class":100,"line":138},[98,3312,3313],{"class":104},"\u002F\u002F Проверка:\n",[98,3315,3316,3318,3320,3322,3325,3327],{"class":100,"line":200},[98,3317,2119],{"class":111},[98,3319,173],{"class":121},[98,3321,1029],{"class":128},[98,3323,3324],{"class":121},"(err, ErrNotFound) { ",[98,3326,442],{"class":111},[98,3328,326],{"class":121},[143,3330,3331],{},"Просто, без полей. Достаточно, когда не нужен контекст.",[84,3333,3335],{"id":3334},"кастомный-тип-контекст","Кастомный тип — контекст",[89,3337,3339],{"className":91,"code":3338,"language":93,"meta":94,"style":94},"\u002F\u002F Пример из stdlib: os.PathError\ntype PathError struct {\n    Op   string  \u002F\u002F \"open\", \"read\"\n    Path string  \u002F\u002F \"\u002Fetc\u002Fconfig\"\n    Err  error   \u002F\u002F underlying error\n}\n\nfunc (e *PathError) Error() string {\n    return e.Op + \" \" + e.Path + \": \" + e.Err.Error()\n}\n\n\u002F\u002F Проверка + доступ к полям:\nvar pathErr *os.PathError\nif errors.As(err, &pathErr) {\n    fmt.Println(pathErr.Path)  \u002F\u002F \"\u002Fetc\u002Fconfig\"\n}\n",[16,3340,3341,3346,3356,3366,3376,3386,3390,3394,3416,3448,3452,3456,3461,3478,3494,3506],{"__ignoreMap":94},[98,3342,3343],{"class":100,"line":101},[98,3344,3345],{"class":104},"\u002F\u002F Пример из stdlib: os.PathError\n",[98,3347,3348,3350,3352,3354],{"class":100,"line":108},[98,3349,112],{"class":111},[98,3351,309],{"class":128},[98,3353,312],{"class":111},[98,3355,122],{"class":121},[98,3357,3358,3361,3363],{"class":100,"line":125},[98,3359,3360],{"class":121},"    Op   ",[98,3362,318],{"class":111},[98,3364,3365],{"class":104},"  \u002F\u002F \"open\", \"read\"\n",[98,3367,3368,3371,3373],{"class":100,"line":138},[98,3369,3370],{"class":121},"    Path ",[98,3372,318],{"class":111},[98,3374,3375],{"class":104},"  \u002F\u002F \"\u002Fetc\u002Fconfig\"\n",[98,3377,3378,3381,3383],{"class":100,"line":200},[98,3379,3380],{"class":121},"    Err  ",[98,3382,18],{"class":111},[98,3384,3385],{"class":104},"   \u002F\u002F underlying error\n",[98,3387,3388],{"class":100,"line":304},[98,3389,141],{"class":121},[98,3391,3392],{"class":100,"line":329},[98,3393,192],{"emptyLinePlaceholder":191},[98,3395,3396,3398,3400,3402,3404,3406,3408,3410,3412,3414],{"class":100,"line":352},[98,3397,414],{"class":111},[98,3399,52],{"class":121},[98,3401,903],{"class":422},[98,3403,707],{"class":111},[98,3405,69],{"class":128},[98,3407,289],{"class":121},[98,3409,66],{"class":128},[98,3411,132],{"class":121},[98,3413,318],{"class":111},[98,3415,122],{"class":121},[98,3417,3418,3420,3423,3426,3429,3432,3435,3437,3440,3442,3444,3446],{"class":100,"line":357},[98,3419,555],{"class":111},[98,3421,3422],{"class":121}," e.Op ",[98,3424,3425],{"class":111},"+",[98,3427,3428],{"class":182}," \" \"",[98,3430,3431],{"class":111}," +",[98,3433,3434],{"class":121}," e.Path ",[98,3436,3425],{"class":111},[98,3438,3439],{"class":182}," \": \"",[98,3441,3431],{"class":111},[98,3443,1993],{"class":121},[98,3445,66],{"class":128},[98,3447,521],{"class":121},[98,3449,3450],{"class":100,"line":363},[98,3451,141],{"class":121},[98,3453,3454],{"class":100,"line":381},[98,3455,192],{"emptyLinePlaceholder":191},[98,3457,3458],{"class":100,"line":586},[98,3459,3460],{"class":104},"\u002F\u002F Проверка + доступ к полям:\n",[98,3462,3463,3465,3468,3470,3473,3475],{"class":100,"line":595},[98,3464,247],{"class":111},[98,3466,3467],{"class":121}," pathErr ",[98,3469,707],{"class":111},[98,3471,3472],{"class":128},"os",[98,3474,1460],{"class":121},[98,3476,3477],{"class":128},"PathError\n",[98,3479,3480,3482,3484,3486,3489,3491],{"class":100,"line":1213},[98,3481,2119],{"class":111},[98,3483,173],{"class":121},[98,3485,2124],{"class":128},[98,3487,3488],{"class":121},"(err, ",[98,3490,755],{"class":111},[98,3492,3493],{"class":121},"pathErr) {\n",[98,3495,3496,3498,3500,3503],{"class":100,"line":1219},[98,3497,2137],{"class":121},[98,3499,857],{"class":128},[98,3501,3502],{"class":121},"(pathErr.Path)  ",[98,3504,3505],{"class":104},"\u002F\u002F \"\u002Fetc\u002Fconfig\"\n",[98,3507,3508],{"class":100,"line":1228},[98,3509,141],{"class":121},[143,3511,3512],{},"Неудобно складывать Op, Path в текст ошибки и потом парсить обратно. Проще — отдельный тип.",[84,3514,3516],{"id":3515},"оба-создают-зависимость","Оба создают зависимость",[89,3518,3520],{"className":91,"code":3519,"language":93,"meta":94,"style":94},"import \"myapp\u002Fstorage\"\n\n\u002F\u002F Для проверки sentinel — нужен импорт storage\nif errors.Is(err, storage.ErrNotFound) { ... }\n\n\u002F\u002F Для проверки типа — тоже нужен импорт storage\nvar dbErr *storage.DatabaseError\nif errors.As(err, &dbErr) { ... }\n",[16,3521,3522,3536,3540,3545,3560,3564,3569,3584],{"__ignoreMap":94},[98,3523,3524,3527,3530,3533],{"class":100,"line":101},[98,3525,3526],{"class":111},"import",[98,3528,3529],{"class":182}," \"",[98,3531,3532],{"class":128},"myapp\u002Fstorage",[98,3534,3535],{"class":182},"\"\n",[98,3537,3538],{"class":100,"line":108},[98,3539,192],{"emptyLinePlaceholder":191},[98,3541,3542],{"class":100,"line":125},[98,3543,3544],{"class":104},"\u002F\u002F Для проверки sentinel — нужен импорт storage\n",[98,3546,3547,3549,3551,3553,3556,3558],{"class":100,"line":138},[98,3548,2119],{"class":111},[98,3550,173],{"class":121},[98,3552,1029],{"class":128},[98,3554,3555],{"class":121},"(err, storage.ErrNotFound) { ",[98,3557,442],{"class":111},[98,3559,326],{"class":121},[98,3561,3562],{"class":100,"line":200},[98,3563,192],{"emptyLinePlaceholder":191},[98,3565,3566],{"class":100,"line":304},[98,3567,3568],{"class":104},"\u002F\u002F Для проверки типа — тоже нужен импорт storage\n",[98,3570,3571,3573,3575,3577,3580,3582],{"class":100,"line":329},[98,3572,247],{"class":111},[98,3574,2109],{"class":121},[98,3576,707],{"class":111},[98,3578,3579],{"class":128},"storage",[98,3581,1460],{"class":121},[98,3583,2114],{"class":128},[98,3585,3586,3588,3590,3592,3594,3596,3599,3601],{"class":100,"line":352},[98,3587,2119],{"class":111},[98,3589,173],{"class":121},[98,3591,2124],{"class":128},[98,3593,3488],{"class":121},[98,3595,755],{"class":111},[98,3597,3598],{"class":121},"dbErr) { ",[98,3600,442],{"class":111},[98,3602,326],{"class":121},[84,3604,3606],{"id":3605},"проверка-по-поведению-разрыв-зависимости","Проверка по поведению — разрыв зависимости",[89,3608,3610],{"className":91,"code":3609,"language":93,"meta":94,"style":94},"\u002F\u002F Пакет storage — ошибка с поведением\ntype FSError struct{ path string }\nfunc (e *FSError) Error() string { return \"fs: \" + e.path }\nfunc (e *FSError) Path() string  { return e.path }\n\n\u002F\u002F Другой пакет — НЕ импортирует storage!\nfunc isPathError(err error) bool {\n    _, ok := err.(interface{ Path() string })  \u002F\u002F анонимный интерфейс\n    return ok\n}\n",[16,3611,3612,3617,3633,3666,3694,3698,3703,3723,3751,3758],{"__ignoreMap":94},[98,3613,3614],{"class":100,"line":101},[98,3615,3616],{"class":104},"\u002F\u002F Пакет storage — ошибка с поведением\n",[98,3618,3619,3621,3624,3626,3629,3631],{"class":100,"line":108},[98,3620,112],{"class":111},[98,3622,3623],{"class":128}," FSError",[98,3625,312],{"class":111},[98,3627,3628],{"class":121},"{ path ",[98,3630,318],{"class":111},[98,3632,326],{"class":121},[98,3634,3635,3637,3639,3641,3643,3646,3648,3650,3652,3654,3656,3658,3661,3663],{"class":100,"line":125},[98,3636,414],{"class":111},[98,3638,52],{"class":121},[98,3640,903],{"class":422},[98,3642,707],{"class":111},[98,3644,3645],{"class":128},"FSError",[98,3647,289],{"class":121},[98,3649,66],{"class":128},[98,3651,132],{"class":121},[98,3653,318],{"class":111},[98,3655,1987],{"class":121},[98,3657,1990],{"class":111},[98,3659,3660],{"class":182}," \"fs: \"",[98,3662,3431],{"class":111},[98,3664,3665],{"class":121}," e.path }\n",[98,3667,3668,3670,3672,3674,3676,3678,3680,3683,3685,3687,3690,3692],{"class":100,"line":138},[98,3669,414],{"class":111},[98,3671,52],{"class":121},[98,3673,903],{"class":422},[98,3675,707],{"class":111},[98,3677,3645],{"class":128},[98,3679,289],{"class":121},[98,3681,3682],{"class":128},"Path",[98,3684,132],{"class":121},[98,3686,318],{"class":111},[98,3688,3689],{"class":121},"  { ",[98,3691,1990],{"class":111},[98,3693,3665],{"class":121},[98,3695,3696],{"class":100,"line":200},[98,3697,192],{"emptyLinePlaceholder":191},[98,3699,3700],{"class":100,"line":304},[98,3701,3702],{"class":104},"\u002F\u002F Другой пакет — НЕ импортирует storage!\n",[98,3704,3705,3707,3710,3712,3714,3716,3718,3721],{"class":100,"line":329},[98,3706,414],{"class":111},[98,3708,3709],{"class":128}," isPathError",[98,3711,179],{"class":121},[98,3713,51],{"class":422},[98,3715,115],{"class":111},[98,3717,289],{"class":121},[98,3719,3720],{"class":111},"bool",[98,3722,122],{"class":121},[98,3724,3725,3728,3730,3733,3736,3739,3741,3743,3745,3748],{"class":100,"line":352},[98,3726,3727],{"class":121},"    _, ok ",[98,3729,170],{"class":111},[98,3731,3732],{"class":121}," err.(",[98,3734,3735],{"class":111},"interface",[98,3737,3738],{"class":121},"{ ",[98,3740,3682],{"class":128},[98,3742,132],{"class":121},[98,3744,318],{"class":111},[98,3746,3747],{"class":121}," })  ",[98,3749,3750],{"class":104},"\u002F\u002F анонимный интерфейс\n",[98,3752,3753,3755],{"class":100,"line":357},[98,3754,555],{"class":111},[98,3756,3757],{"class":121}," ok\n",[98,3759,3760],{"class":100,"line":363},[98,3761,141],{"class":121},[143,3763,3764],{},"Программист должен знать поведение (какой метод проверять), но коду не нужен импорт. Для исключительных случаев, когда важно разорвать зависимость.",[84,3766,3768],{"id":3767},"ошибки-публичное-api","Ошибки = публичное API",[143,3770,3771],{},"Публичные sentinel'ы и типы ошибок — такая же часть API, как функции и структуры. Менять их — breaking change. Относиться бережно.",[81,3773],{},[10,3775,3776,3783,3786,3798,3807],{},[13,3777,3778,3779,3782],{},"multierror — пакет для ",[38,3780,3781],{},"списка ошибок",", когда одной недостаточно",[13,3784,3785],{},"сценарий: несколько независимых ветвей могут давать ошибки (обработка среза, параллельные запросы)",[13,3787,3788,3791,3792,3794,3795],{},[16,3789,3790],{},"Append"," для накопления, результат — обычная ",[16,3793,18],{}," с методом ",[16,3796,3797],{},"Error()",[13,3799,3800,3801,56,3803,56,3805],{},"полностью совместим с ",[16,3802,638],{},[16,3804,1786],{},[16,3806,1190],{},[13,3808,3809,3810,3813],{},"компактнее, чем возвращать ",[16,3811,3812],{},"(error, error, error)"," и проверять каждую",[81,3815],{},[84,3817,3819],{"id":3818},"зачем","Зачем",[89,3821,3823],{"className":91,"code":3822,"language":93,"meta":94,"style":94},"\u002F\u002F Обрабатываем срез — на каждом элементе может быть сбой\nfunc ProcessAll(items []Item) error {\n    var result error\n    for _, item := range items {\n        if err := process(item); err != nil {\n            result = multierror.Append(result, err)\n        }\n    }\n    return result  \u002F\u002F nil если ошибок не было, иначе список\n}\n",[16,3824,3825,3830,3854,3864,3880,3899,3914,3919,3923,3933],{"__ignoreMap":94},[98,3826,3827],{"class":100,"line":101},[98,3828,3829],{"class":104},"\u002F\u002F Обрабатываем срез — на каждом элементе может быть сбой\n",[98,3831,3832,3834,3837,3839,3842,3845,3848,3850,3852],{"class":100,"line":108},[98,3833,414],{"class":111},[98,3835,3836],{"class":128}," ProcessAll",[98,3838,179],{"class":121},[98,3840,3841],{"class":422},"items",[98,3843,3844],{"class":121}," []",[98,3846,3847],{"class":128},"Item",[98,3849,289],{"class":121},[98,3851,18],{"class":111},[98,3853,122],{"class":121},[98,3855,3856,3859,3862],{"class":100,"line":125},[98,3857,3858],{"class":111},"    var",[98,3860,3861],{"class":121}," result ",[98,3863,1959],{"class":111},[98,3865,3866,3869,3872,3874,3877],{"class":100,"line":138},[98,3867,3868],{"class":111},"    for",[98,3870,3871],{"class":121}," _, item ",[98,3873,170],{"class":111},[98,3875,3876],{"class":111}," range",[98,3878,3879],{"class":121}," items {\n",[98,3881,3882,3884,3886,3888,3890,3893,3895,3897],{"class":100,"line":200},[98,3883,2939],{"class":111},[98,3885,529],{"class":121},[98,3887,170],{"class":111},[98,3889,3061],{"class":128},[98,3891,3892],{"class":121},"(item); err ",[98,3894,532],{"class":111},[98,3896,535],{"class":218},[98,3898,122],{"class":121},[98,3900,3901,3904,3906,3909,3911],{"class":100,"line":304},[98,3902,3903],{"class":121},"            result ",[98,3905,253],{"class":111},[98,3907,3908],{"class":121}," multierror.",[98,3910,3790],{"class":128},[98,3912,3913],{"class":121},"(result, err)\n",[98,3915,3916],{"class":100,"line":329},[98,3917,3918],{"class":121},"        }\n",[98,3920,3921],{"class":100,"line":352},[98,3922,550],{"class":121},[98,3924,3925,3927,3930],{"class":100,"line":357},[98,3926,555],{"class":111},[98,3928,3929],{"class":121}," result  ",[98,3931,3932],{"class":104},"\u002F\u002F nil если ошибок не было, иначе список\n",[98,3934,3935],{"class":100,"line":363},[98,3936,141],{"class":121},[143,3938,3939],{},"Не хотим останавливаться на первой ошибке — обрабатываем весь срез, собираем все ошибки.",[143,3941,3942,3945],{},[16,3943,3944],{},"multierror.Append(nil, ...)"," возвращает nil если все переданные ошибки nil. Если хотя бы одна не nil — возвращает multierror.",[84,3947,3949],{"id":3948},"совместимость-с-isasunwrap","Совместимость с Is\u002FAs\u002FUnwrap",[89,3951,3953],{"className":91,"code":3952,"language":93,"meta":94,"style":94},"err1 := errors.New(\"db timeout\")\nerr2 := errors.New(\"cache miss\")\nvar ErrFatal = errors.New(\"fatal\")\n\nmulti := multierror.Append(nil, err1, err2, ErrFatal)\n\n\u002F\u002F Оборачивание тоже работает\nwrapped := fmt.Errorf(\"batch failed: %w\", multi)\n\n\u002F\u002F Is\u002FAs проходят по всему списку\nfmt.Println(errors.Is(wrapped, ErrFatal))  \u002F\u002F true\n",[16,3954,3955,3972,3989,4009,4013,4031,4035,4040,4062,4066,4071],{"__ignoreMap":94},[98,3956,3957,3959,3961,3963,3965,3967,3970],{"class":100,"line":101},[98,3958,1311],{"class":121},[98,3960,170],{"class":111},[98,3962,173],{"class":121},[98,3964,176],{"class":128},[98,3966,179],{"class":121},[98,3968,3969],{"class":182},"\"db timeout\"",[98,3971,186],{"class":121},[98,3973,3974,3976,3978,3980,3982,3984,3987],{"class":100,"line":108},[98,3975,1329],{"class":121},[98,3977,170],{"class":111},[98,3979,173],{"class":121},[98,3981,176],{"class":128},[98,3983,179],{"class":121},[98,3985,3986],{"class":182},"\"cache miss\"",[98,3988,186],{"class":121},[98,3990,3991,3993,3996,3998,4000,4002,4004,4007],{"class":100,"line":125},[98,3992,247],{"class":111},[98,3994,3995],{"class":121}," ErrFatal ",[98,3997,253],{"class":111},[98,3999,173],{"class":121},[98,4001,176],{"class":128},[98,4003,179],{"class":121},[98,4005,4006],{"class":182},"\"fatal\"",[98,4008,186],{"class":121},[98,4010,4011],{"class":100,"line":138},[98,4012,192],{"emptyLinePlaceholder":191},[98,4014,4015,4018,4020,4022,4024,4026,4028],{"class":100,"line":200},[98,4016,4017],{"class":121},"multi ",[98,4019,170],{"class":111},[98,4021,3908],{"class":121},[98,4023,3790],{"class":128},[98,4025,179],{"class":121},[98,4027,1290],{"class":218},[98,4029,4030],{"class":121},", err1, err2, ErrFatal)\n",[98,4032,4033],{"class":100,"line":304},[98,4034,192],{"emptyLinePlaceholder":191},[98,4036,4037],{"class":100,"line":329},[98,4038,4039],{"class":104},"\u002F\u002F Оборачивание тоже работает\n",[98,4041,4042,4044,4046,4048,4050,4052,4055,4057,4059],{"class":100,"line":352},[98,4043,997],{"class":121},[98,4045,170],{"class":111},[98,4047,207],{"class":121},[98,4049,210],{"class":128},[98,4051,179],{"class":121},[98,4053,4054],{"class":182},"\"batch failed: ",[98,4056,1011],{"class":218},[98,4058,1014],{"class":182},[98,4060,4061],{"class":121},", multi)\n",[98,4063,4064],{"class":100,"line":357},[98,4065,192],{"emptyLinePlaceholder":191},[98,4067,4068],{"class":100,"line":363},[98,4069,4070],{"class":104},"\u002F\u002F Is\u002FAs проходят по всему списку\n",[98,4072,4073,4075,4077,4079,4081,4084],{"class":100,"line":381},[98,4074,854],{"class":121},[98,4076,857],{"class":128},[98,4078,1026],{"class":121},[98,4080,1029],{"class":128},[98,4082,4083],{"class":121},"(wrapped, ErrFatal))  ",[98,4085,1035],{"class":104},[143,4087,4088,4090],{},[16,4089,638],{}," при поиске проходит по всему списку ошибок внутри multierror.",[84,4092,4094],{"id":4093},"почему-не-error-error-error","Почему не (error, error, error)",[89,4096,4098],{"className":91,"code":4097,"language":93,"meta":94,"style":94},"\u002F\u002F ❌ загромождает — на каждом уровне проброса проверяй все три\nfunc DoWork() (error, error, error) { ... }\n\n\u002F\u002F ✅ одна ошибка — внутри список\nfunc DoWork() error {\n    var errs error\n    errs = multierror.Append(errs, step1())\n    errs = multierror.Append(errs, step2())\n    return errs\n}\n",[16,4099,4100,4105,4131,4135,4140,4152,4161,4181,4198,4205],{"__ignoreMap":94},[98,4101,4102],{"class":100,"line":101},[98,4103,4104],{"class":104},"\u002F\u002F ❌ загромождает — на каждом уровне проброса проверяй все три\n",[98,4106,4107,4109,4112,4115,4117,4119,4121,4123,4125,4127,4129],{"class":100,"line":108},[98,4108,414],{"class":111},[98,4110,4111],{"class":128}," DoWork",[98,4113,4114],{"class":121},"() (",[98,4116,18],{"class":111},[98,4118,56],{"class":121},[98,4120,18],{"class":111},[98,4122,56],{"class":121},[98,4124,18],{"class":111},[98,4126,439],{"class":121},[98,4128,442],{"class":111},[98,4130,326],{"class":121},[98,4132,4133],{"class":100,"line":125},[98,4134,192],{"emptyLinePlaceholder":191},[98,4136,4137],{"class":100,"line":138},[98,4138,4139],{"class":104},"\u002F\u002F ✅ одна ошибка — внутри список\n",[98,4141,4142,4144,4146,4148,4150],{"class":100,"line":200},[98,4143,414],{"class":111},[98,4145,4111],{"class":128},[98,4147,132],{"class":121},[98,4149,18],{"class":111},[98,4151,122],{"class":121},[98,4153,4154,4156,4159],{"class":100,"line":304},[98,4155,3858],{"class":111},[98,4157,4158],{"class":121}," errs ",[98,4160,1959],{"class":111},[98,4162,4163,4166,4168,4170,4172,4175,4178],{"class":100,"line":329},[98,4164,4165],{"class":121},"    errs ",[98,4167,253],{"class":111},[98,4169,3908],{"class":121},[98,4171,3790],{"class":128},[98,4173,4174],{"class":121},"(errs, ",[98,4176,4177],{"class":128},"step1",[98,4179,4180],{"class":121},"())\n",[98,4182,4183,4185,4187,4189,4191,4193,4196],{"class":100,"line":352},[98,4184,4165],{"class":121},[98,4186,253],{"class":111},[98,4188,3908],{"class":121},[98,4190,3790],{"class":128},[98,4192,4174],{"class":121},[98,4194,4195],{"class":128},"step2",[98,4197,4180],{"class":121},[98,4199,4200,4202],{"class":100,"line":357},[98,4201,555],{"class":111},[98,4203,4204],{"class":121}," errs\n",[98,4206,4207],{"class":100,"line":363},[98,4208,141],{"class":121},[81,4210],{},[10,4212,4213,4224,4231,4238],{},[13,4214,4215,4216,4219,4220,4223],{},"стандартный пакет ",[16,4217,4218],{},"errors"," ",[38,4221,4222],{},"не даёт"," стектрейс",[13,4225,4226,4227,4230],{},"пакет ",[16,4228,4229],{},"github.com\u002Fpkg\u002Ferrors"," — сохраняет стек при создании ошибки",[13,4232,4233,4234,4237],{},"снятие стектрейса — ",[38,4235,4236],{},"не бесплатно",", бенчмарк показывает существенный оверхед",[13,4239,4240],{},"не злоупотреблять в горячих местах и логах — логи стоят дорого",[81,4242],{},[84,4244,4246],{"id":4245},"стандартный-пакет-без-стектрейса","Стандартный пакет — без стектрейса",[89,4248,4250],{"className":91,"code":4249,"language":93,"meta":94,"style":94},"err := errors.New(\"something failed\")\n\u002F\u002F Нет способа получить стек вызова — только текст\n",[16,4251,4252,4269],{"__ignoreMap":94},[98,4253,4254,4256,4258,4260,4262,4264,4267],{"class":100,"line":101},[98,4255,167],{"class":121},[98,4257,170],{"class":111},[98,4259,173],{"class":121},[98,4261,176],{"class":128},[98,4263,179],{"class":121},[98,4265,4266],{"class":182},"\"something failed\"",[98,4268,186],{"class":121},[98,4270,4271],{"class":100,"line":108},[98,4272,4273],{"class":104},"\u002F\u002F Нет способа получить стек вызова — только текст\n",[143,4275,4276,4277,4280],{},"Стандартный ",[16,4278,4279],{},"errors.New"," создаёт ошибку только с текстом — никакого стека вызовов.",[84,4282,4284],{"id":4283},"githubcompkgerrors-со-стектрейсом","github.com\u002Fpkg\u002Ferrors — со стектрейсом",[89,4286,4288],{"className":91,"code":4287,"language":93,"meta":94,"style":94},"import pkgerrors \"github.com\u002Fpkg\u002Ferrors\"\n\nerr := pkgerrors.New(\"something failed\")\n\u002F\u002F Внутри сохраняется стек вызова на момент создания\n\nfmt.Printf(\"%+v\\n\", err)\\n\u002F\u002F something failed\n\u002F\u002F main.process\n\u002F\u002F     \u002Fapp\u002Fmain.go:15\n\u002F\u002F main.main\n\u002F\u002F     \u002Fapp\u002Fmain.go:8\n",[16,4289,4290,4303,4307,4324,4329,4333,4354,4359,4364,4369],{"__ignoreMap":94},[98,4291,4292,4294,4297,4299,4301],{"class":100,"line":101},[98,4293,3526],{"class":111},[98,4295,4296],{"class":121}," pkgerrors ",[98,4298,1014],{"class":182},[98,4300,4229],{"class":128},[98,4302,3535],{"class":182},[98,4304,4305],{"class":100,"line":108},[98,4306,192],{"emptyLinePlaceholder":191},[98,4308,4309,4311,4313,4316,4318,4320,4322],{"class":100,"line":125},[98,4310,167],{"class":121},[98,4312,170],{"class":111},[98,4314,4315],{"class":121}," pkgerrors.",[98,4317,176],{"class":128},[98,4319,179],{"class":121},[98,4321,4266],{"class":182},[98,4323,186],{"class":121},[98,4325,4326],{"class":100,"line":138},[98,4327,4328],{"class":104},"\u002F\u002F Внутри сохраняется стек вызова на момент создания\n",[98,4330,4331],{"class":100,"line":200},[98,4332,192],{"emptyLinePlaceholder":191},[98,4334,4335,4337,4339,4341,4343,4346,4348,4351],{"class":100,"line":304},[98,4336,854],{"class":121},[98,4338,2527],{"class":128},[98,4340,179],{"class":121},[98,4342,1014],{"class":182},[98,4344,4345],{"class":218},"%+v\\n",[98,4347,1014],{"class":182},[98,4349,4350],{"class":121},", err)\\n",[98,4352,4353],{"class":104},"\u002F\u002F something failed\n",[98,4355,4356],{"class":100,"line":329},[98,4357,4358],{"class":104},"\u002F\u002F main.process\n",[98,4360,4361],{"class":100,"line":352},[98,4362,4363],{"class":104},"\u002F\u002F     \u002Fapp\u002Fmain.go:15\n",[98,4365,4366],{"class":100,"line":357},[98,4367,4368],{"class":104},"\u002F\u002F main.main\n",[98,4370,4371],{"class":100,"line":363},[98,4372,4373],{"class":104},"\u002F\u002F     \u002Fapp\u002Fmain.go:8\n",[143,4375,4376,4379,4380,4383],{},[16,4377,4378],{},"%+v"," — специальный формат для ",[16,4381,4382],{},"pkg\u002Ferrors",", выводит текст ошибки + полный стектрейс с именами функций и номерами строк.",[84,4385,4387],{"id":4386},"оверхед","Оверхед",[89,4389,4392],{"className":4390,"code":4391,"language":3043},[3041],"BenchmarkStandardError    ~50 ns\u002Fop     0 allocs\nBenchmarkPkgErrors        ~2000 ns\u002Fop   3 allocs\n",[16,4393,4391],{"__ignoreMap":94},[143,4395,4396,4397,4399,4400,4403],{},"Снять стектрейс ≈ в 40 раз дороже стандартного ",[16,4398,4279],{},". Причина: ",[16,4401,4402],{},"runtime.Callers"," — проход по стеку, аллокации.",[84,4405,4407],{"id":4406},"когда-использовать","Когда использовать",[143,4409,4410,4411,4414],{},"Стектрейс полезен для ",[38,4412,4413],{},"отладки",", но не нужен всегда. Если в горячем цикле создаёте ошибки со стектрейсом — это оверхед. Если логируете стектрейсы в каждом безобидном логе — логи разрастаются и стоят дорого.",[81,4416],{},[4418,4419,4420],"style",{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}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 pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":94,"searchDepth":108,"depth":108,"links":4422},[4423,4424,4425,4426,4427,4428,4429,4430,4431,4432,4433,4434,4435,4436,4437,4438,4439,4440,4441,4442,4443,4444,4445,4446,4447,4448,4449,4450,4451,4452,4453,4454,4455,4456,4457,4458,4459,4460,4461,4462,4463],{"id":86,"depth":108,"text":87},{"id":151,"depth":108,"text":152},{"id":231,"depth":108,"text":232},{"id":398,"depth":108,"text":399},{"id":484,"depth":108,"text":485},{"id":647,"depth":108,"text":648},{"id":806,"depth":108,"text":807},{"id":874,"depth":108,"text":875},{"id":986,"depth":108,"text":987},{"id":1041,"depth":108,"text":1042},{"id":1100,"depth":108,"text":1101},{"id":1300,"depth":108,"text":1301},{"id":1463,"depth":108,"text":1464},{"id":1675,"depth":108,"text":1676},{"id":1815,"depth":108,"text":1816},{"id":1927,"depth":108,"text":1928},{"id":2157,"depth":108,"text":2158},{"id":2246,"depth":108,"text":2247},{"id":2347,"depth":108,"text":2348},{"id":2423,"depth":108,"text":2424},{"id":2458,"depth":108,"text":2459},{"id":2772,"depth":108,"text":2773},{"id":2786,"depth":108,"text":2787},{"id":2908,"depth":108,"text":2909},{"id":3036,"depth":108,"text":3037},{"id":3048,"depth":108,"text":3049},{"id":3130,"depth":108,"text":3131},{"id":3202,"depth":108,"text":3203},{"id":3216,"depth":108,"text":3217},{"id":3261,"depth":108,"text":3262},{"id":3334,"depth":108,"text":3335},{"id":3515,"depth":108,"text":3516},{"id":3605,"depth":108,"text":3606},{"id":3767,"depth":108,"text":3768},{"id":3818,"depth":108,"text":3819},{"id":3948,"depth":108,"text":3949},{"id":4093,"depth":108,"text":4094},{"id":4245,"depth":108,"text":4246},{"id":4283,"depth":108,"text":4284},{"id":4386,"depth":108,"text":4387},{"id":4406,"depth":108,"text":4407},"beginner","md",{},"functions-errors","goroutines-channels","\u002F02-functions-errors\u002F03-errors","defer",{"title":5,"description":94},"02-functions-errors\u002F03-errors\u002Findex",[4218,18,3229,4474,1029,2124,2878,2891,1764],"wrap","zB4pAjWE2cqxvNiVrJHaeTNpCFhresy9CXdX_SLtaMI",1776280770000]