[{"data":1,"prerenderedAt":11941},["ShallowReactive",2],{"navigation":3,"blog-page":26,"blogs":38},[4],{"title":5,"path":6,"stem":7,"children":8,"page":25},"Blog","\u002Fblog","blog",[9,13,17,21],{"title":10,"path":11,"stem":12},"Array ou slice? Capacidade, crescimento e as armadilhas do append","\u002Fblog\u002Farrays-e-slices-em-go-capacidade-crescimento-e-append","blog\u002Farrays-e-slices-em-go-capacidade-crescimento-e-append",{"title":14,"path":15,"stem":16},"Stack ou heap? Entendendo o escape analysis do Go","\u002Fblog\u002Fescape-analysis-em-go-stack-ou-heap","blog\u002Fescape-analysis-em-go-stack-ou-heap",{"title":18,"path":19,"stem":20},"Por que Go copia tudo? Zero Values e Pointer Semantics","\u002Fblog\u002Fgo-zero-values-e-value-semantics-vs-pointer-semantics","blog\u002Fgo-zero-values-e-value-semantics-vs-pointer-semantics",{"title":22,"path":23,"stem":24},"Nil map ou map vazio? Zero value, ordem de iteração e concorrência em Go","\u002Fblog\u002Fmaps-em-go-zero-value-ordem-de-iteracao-e-concorrencia","blog\u002Fmaps-em-go-zero-value-ordem-de-iteracao-e-concorrencia",false,{"id":27,"title":28,"body":29,"description":30,"extension":31,"links":29,"meta":32,"navigation":33,"path":6,"seo":34,"stem":7,"__hash__":37},"pages\u002Fblog.yml","Artigos",null,"Estudos e anotações sobre desenvolvimento de software.","yml",{},true,{"title":35,"description":36},"Jairo Blatt","Artigos práticos arquitetura e engenharia de software, escritos por Jairo Blatt.","3ePOnPtHl3D7pUC9JNWoZAvvnXyPb5XgdOBUisvxgxw",[39,3153,6809,10435],{"id":40,"title":22,"author":29,"body":41,"date":3146,"description":3147,"extension":3148,"image":3149,"meta":3150,"minRead":2335,"navigation":33,"path":23,"seo":3151,"stem":24,"tags":29,"updatedAt":29,"__hash__":3152},"blog\u002Fblog\u002Fmaps-em-go-zero-value-ordem-de-iteracao-e-concorrencia.md",{"type":42,"value":43,"toc":3132},"minimark",[44,63,66,224,230,271,279,287,302,314,319,325,347,360,375,382,397,401,410,428,431,608,614,627,668,673,683,706,709,770,787,970,984,1026,1030,1036,1158,1164,1170,1187,1196,1206,1209,1244,1249,1285,1294,1300,1305,1319,1491,1497,1506,1526,1635,1641,1650,1677,1681,1684,1703,1710,1713,1940,1946,1953,1959,1987,1990,2002,2032,2036,2043,2063,2076,2338,2343,2367,2370,2376,2391,2404,2419,2423,2431,2531,2537,2555,2591,2606,2614,2696,2709,2729,2733,2855,2859,2862,2941,2946,2950,3128],[45,46,47,48,53,54,58,59,62],"p",{},"No artigo sobre ",[49,50,52],"a",{"href":51},"\u002Fgo-zero-values-e-value-semantics-vs-pointer-semantics","zero values e pointer semantics"," eu mencionei de passagem que nem todo zero value é útil de imediato, e que o ",[55,56,57],"code",{},"map"," é o exemplo clássico: seu zero value é ",[55,60,61],{},"nil",", e escrever nele causa panic. Fechei o assunto em duas frases. Ele merecia mais espaço, e é este artigo aqui.",[45,64,65],{},"Vamos começar pelo bug, como de costume. Um rate limiter simples, guardado por cliente:",[67,68,73],"pre",{"className":69,"code":70,"language":71,"meta":72,"style":72},"language-go shiki shiki-themes material-theme-lighter github-light github-dark","type RateLimiter struct {\n    hits map[string]int\n}\n\nfunc (r *RateLimiter) Allow(client string) bool {\n    r.hits[client]++\n    return r.hits[client] \u003C= 100\n}\n","go","",[55,74,75,95,117,123,129,171,192,219],{"__ignoreMap":72},[76,77,80,84,88,91],"span",{"class":78,"line":79},"line",1,[76,81,83],{"class":82},"smGrS","type",[76,85,87],{"class":86},"sbgvK"," RateLimiter",[76,89,90],{"class":82}," struct",[76,92,94],{"class":93},"sP7_E"," {\n",[76,96,98,102,104,107,111,114],{"class":78,"line":97},2,[76,99,101],{"class":100},"su5hD","    hits ",[76,103,57],{"class":82},[76,105,106],{"class":93},"[",[76,108,110],{"class":109},"sbsja","string",[76,112,113],{"class":93},"]",[76,115,116],{"class":109},"int\n",[76,118,120],{"class":78,"line":119},3,[76,121,122],{"class":93},"}\n",[76,124,126],{"class":78,"line":125},4,[76,127,128],{"emptyLinePlaceholder":33},"\n",[76,130,132,135,138,142,145,148,151,155,158,161,164,166,169],{"class":78,"line":131},5,[76,133,134],{"class":82},"func",[76,136,137],{"class":93}," (",[76,139,141],{"class":140},"s99_P","r ",[76,143,144],{"class":82},"*",[76,146,147],{"class":86},"RateLimiter",[76,149,150],{"class":93},")",[76,152,154],{"class":153},"sGLFI"," Allow",[76,156,157],{"class":93},"(",[76,159,160],{"class":140},"client",[76,162,163],{"class":109}," string",[76,165,150],{"class":93},[76,167,168],{"class":109}," bool",[76,170,94],{"class":93},[76,172,174,177,180,183,185,187,189],{"class":78,"line":173},6,[76,175,176],{"class":100},"    r",[76,178,179],{"class":93},".",[76,181,182],{"class":100},"hits",[76,184,106],{"class":93},[76,186,160],{"class":100},[76,188,113],{"class":93},[76,190,191],{"class":82},"++\n",[76,193,195,199,202,204,206,208,210,212,215],{"class":78,"line":194},7,[76,196,198],{"class":197},"sVHd0","    return",[76,200,201],{"class":100}," r",[76,203,179],{"class":93},[76,205,182],{"class":100},[76,207,106],{"class":93},[76,209,160],{"class":100},[76,211,113],{"class":93},[76,213,214],{"class":82}," \u003C=",[76,216,218],{"class":217},"srdBf"," 100\n",[76,220,222],{"class":78,"line":221},8,[76,223,122],{"class":93},[45,225,226,227,229],{},"Nada aqui parece perigoso: um struct, um método, um ",[55,228,57],{}," como campo. O problema aparece no uso mais óbvio possível:",[67,231,233],{"className":69,"code":232,"language":71,"meta":72,"style":72},"var limiter RateLimiter\nlimiter.Allow(\"client-1\")\n",[55,234,235,246],{"__ignoreMap":72},[76,236,237,240,243],{"class":78,"line":79},[76,238,239],{"class":82},"var",[76,241,242],{"class":100}," limiter ",[76,244,245],{"class":86},"RateLimiter\n",[76,247,248,251,253,256,258,262,266,268],{"class":78,"line":97},[76,249,250],{"class":100},"limiter",[76,252,179],{"class":93},[76,254,255],{"class":153},"Allow",[76,257,157],{"class":93},[76,259,261],{"class":260},"sjJ54","\"",[76,263,265],{"class":264},"s_sjI","client-1",[76,267,261],{"class":260},[76,269,270],{"class":93},")\n",[67,272,277],{"className":273,"code":275,"language":276,"meta":72},[274],"language-text","panic: assignment to entry in nil map\n","text",[55,278,275],{"__ignoreMap":72},[45,280,281,284,285,179],{},[55,282,283],{},"var limiter RateLimiter"," é exatamente o padrão que o artigo sobre zero values elogiou: nenhuma inicialização, struct pronta para uso. Só que \"pronta para uso\" tinha uma exceção que eu não expliquei direito na época, e ela mora no campo ",[55,286,182],{},[288,289,290],"note",{},[45,291,292,293,297,298,301],{},"Tudo aqui foi verificado com ",[294,295,296],"strong",{},"Go 1.26",", rodando os exemplos localmente e lendo o código-fonte do runtime (",[55,299,300],{},"internal\u002Fruntime\u002Fmaps","). Onde o comportamento já mudou entre versões, existe uma seção específica sobre isso mais adiante.",[45,303,304,305,307,308,310,311,313],{},"A proposta deste artigo é destrinchar três coisas que costumam ser tratadas como folclore, mas têm mecanismo por trás: o que o zero value de um ",[55,306,57],{}," permite e o que ele proíbe, por que rodar duas vezes sobre o mesmo ",[55,309,57],{}," te dá ordens diferentes, e o que realmente acontece quando duas goroutines mexem no mesmo ",[55,312,57],{}," ao mesmo tempo.",[315,316,318],"h2",{"id":317},"por-baixo-uma-hash-table","Por baixo, uma hash table",[45,320,321,322,324],{},"Um ",[55,323,57],{}," em Go é, por definição, uma coleção não ordenada:",[326,327,328,334],"blockquote",{},[45,329,330,331,333],{},"\"Um map é um grupo não ordenado de elementos de um tipo, chamado tipo do elemento, indexado por um conjunto de chaves únicas de outro tipo, chamado tipo da chave. O valor de um map não inicializado é ",[55,332,61],{},".\"",[45,335,336,337],{},"— ",[49,338,342,343],{"href":339,"rel":340},"https:\u002F\u002Fgo.dev\u002Fref\u002Fspec#Map_types",[341],"nofollow","The Go Programming Language Specification, ",[344,345,346],"em",{},"Map types",[45,348,349,350,353,354,359],{},"\"Não ordenado\" ali não é um detalhe cosmético, é a raiz da Parte 2 deste artigo. Por baixo, a implementação é uma hash table, e desde o ",[294,351,352],{},"Go 1.24"," essa hash table é uma ",[49,355,358],{"href":356,"rel":357},"https:\u002F\u002Fabseil.io\u002Fabout\u002Fdesign\u002Fswisstables",[341],"Swiss Table",":",[326,361,362,368],{},[45,363,364,365,367],{},"\"Diversas melhorias de performance no runtime reduziram o overhead de CPU em 2–3% em média, em um conjunto de benchmarks representativos (...) Essas melhorias incluem uma nova implementação do ",[55,366,57],{}," embutido baseada em Swiss Tables (...)\"",[45,369,336,370],{},[49,371,374],{"href":372,"rel":373},"https:\u002F\u002Fgo.dev\u002Fdoc\u002Fgo1.24",[341],"Go 1.24 Release Notes",[45,376,377,378,381],{},"A troca é reversível em tempo de build (",[55,379,380],{},"GOEXPERIMENT=noswissmap","), o que já é uma boa pista de que isso é implementação, não contrato: nenhum programa correto deveria depender de qual hash table roda por baixo.",[45,383,384,385,388,389,393,394,396],{},"Sobre o tipo da chave, a spec exige que ele seja comparável com ",[55,386,387],{},"==",", e isso já apareceu no ",[49,390,392],{"href":391},"\u002Farrays-e-slices-em-go-capacidade-crescimento-e-append#duas-coisas-diferentes-com-nomes-parecidos","artigo sobre arrays e slices",": é por isso que um array pode ser chave de ",[55,395,57],{}," e um slice não pode. Não vou repetir aquela discussão aqui — vale a pena ler se você pulou.",[315,398,400],{"id":399},"parte-1-o-zero-value-que-engana","Parte 1: o zero value que engana",[45,402,403,404,406,407,409],{},"O zero value de um ",[55,405,57],{}," é ",[55,408,61],{},", e a spec é direta sobre o que isso significa:",[326,411,412,421],{},[45,413,414,415,417,418,420],{},"\"Um ",[55,416,57],{}," ",[55,419,61],{}," é equivalente a um map vazio, exceto que nenhum elemento pode ser adicionado.\"",[45,422,336,423],{},[49,424,342,426],{"href":339,"rel":425},[341],[344,427,346],{},[45,429,430],{},"\"Equivalente a um map vazio, exceto\" é uma frase que vale a pena testar sem confiar de olhos fechados:",[67,432,434],{"className":69,"code":433,"language":71,"meta":72,"style":72},"var m map[string]int\n\nfmt.Println(m[\"missing\"]) \u002F\u002F 0, sem panic\nfmt.Println(len(m))       \u002F\u002F 0\nv, ok := m[\"missing\"]\nfmt.Println(v, ok)        \u002F\u002F 0 false\nfor range m {\n    \u002F\u002F nunca entra aqui\n}\ndelete(m, \"missing\") \u002F\u002F no-op, sem panic\n",[55,435,436,453,457,488,511,539,561,574,579,584],{"__ignoreMap":72},[76,437,438,440,443,445,447,449,451],{"class":78,"line":79},[76,439,239],{"class":82},[76,441,442],{"class":100}," m ",[76,444,57],{"class":82},[76,446,106],{"class":93},[76,448,110],{"class":109},[76,450,113],{"class":93},[76,452,116],{"class":109},[76,454,455],{"class":78,"line":97},[76,456,128],{"emptyLinePlaceholder":33},[76,458,459,462,464,467,469,472,474,476,479,481,484],{"class":78,"line":119},[76,460,461],{"class":100},"fmt",[76,463,179],{"class":93},[76,465,466],{"class":153},"Println",[76,468,157],{"class":93},[76,470,471],{"class":100},"m",[76,473,106],{"class":93},[76,475,261],{"class":260},[76,477,478],{"class":264},"missing",[76,480,261],{"class":260},[76,482,483],{"class":93},"])",[76,485,487],{"class":486},"sutJx"," \u002F\u002F 0, sem panic\n",[76,489,490,492,494,496,498,501,503,505,508],{"class":78,"line":125},[76,491,461],{"class":100},[76,493,179],{"class":93},[76,495,466],{"class":153},[76,497,157],{"class":93},[76,499,500],{"class":153},"len",[76,502,157],{"class":93},[76,504,471],{"class":100},[76,506,507],{"class":93},"))",[76,509,510],{"class":486},"       \u002F\u002F 0\n",[76,512,513,516,519,522,525,528,530,532,534,536],{"class":78,"line":131},[76,514,515],{"class":100},"v",[76,517,518],{"class":93},",",[76,520,521],{"class":100}," ok ",[76,523,524],{"class":82},":=",[76,526,527],{"class":100}," m",[76,529,106],{"class":93},[76,531,261],{"class":260},[76,533,478],{"class":264},[76,535,261],{"class":260},[76,537,538],{"class":93},"]\n",[76,540,541,543,545,547,549,551,553,556,558],{"class":78,"line":173},[76,542,461],{"class":100},[76,544,179],{"class":93},[76,546,466],{"class":153},[76,548,157],{"class":93},[76,550,515],{"class":100},[76,552,518],{"class":93},[76,554,555],{"class":100}," ok",[76,557,150],{"class":93},[76,559,560],{"class":486},"        \u002F\u002F 0 false\n",[76,562,563,566,569,571],{"class":78,"line":194},[76,564,565],{"class":197},"for",[76,567,568],{"class":197}," range",[76,570,442],{"class":100},[76,572,573],{"class":93},"{\n",[76,575,576],{"class":78,"line":221},[76,577,578],{"class":486},"    \u002F\u002F nunca entra aqui\n",[76,580,582],{"class":78,"line":581},9,[76,583,122],{"class":93},[76,585,587,590,592,594,596,599,601,603,605],{"class":78,"line":586},10,[76,588,589],{"class":153},"delete",[76,591,157],{"class":93},[76,593,471],{"class":100},[76,595,518],{"class":93},[76,597,598],{"class":260}," \"",[76,600,478],{"class":264},[76,602,261],{"class":260},[76,604,150],{"class":93},[76,606,607],{"class":486}," \u002F\u002F no-op, sem panic\n",[67,609,612],{"className":610,"code":611,"language":276,"meta":72},[274],"read from nil map: 0\nlen(nil map): 0\ncomma-ok on nil map: 0 false\nrange over nil map iterations: 0\ndelete on nil map: survived\n",[55,613,611],{"__ignoreMap":72},[45,615,616,617,619,620,623,624,626],{},"Leitura, ",[55,618,500],{},", ",[55,621,622],{},"range",", comma-ok e ",[55,625,589],{}," funcionam todos normalmente. A única operação que quebra é a escrita:",[67,628,630],{"className":69,"code":629,"language":71,"meta":72,"style":72},"var m map[string]int\nm[\"missing\"] = 1\n",[55,631,632,648],{"__ignoreMap":72},[76,633,634,636,638,640,642,644,646],{"class":78,"line":79},[76,635,239],{"class":82},[76,637,442],{"class":100},[76,639,57],{"class":82},[76,641,106],{"class":93},[76,643,110],{"class":109},[76,645,113],{"class":93},[76,647,116],{"class":109},[76,649,650,652,654,656,658,660,662,665],{"class":78,"line":97},[76,651,471],{"class":100},[76,653,106],{"class":93},[76,655,261],{"class":260},[76,657,478],{"class":264},[76,659,261],{"class":260},[76,661,113],{"class":93},[76,663,664],{"class":82}," =",[76,666,667],{"class":217}," 1\n",[67,669,671],{"className":670,"code":275,"language":276,"meta":72},[274],[55,672,275],{"__ignoreMap":72},[45,674,675,676,678,679,682],{},"Essa é a exceção da spec, e é ela que pegou o ",[55,677,147],{}," da introdução: ",[55,680,681],{},"r.hits[client]++"," é leitura seguida de escrita, e a escrita é o que derruba o programa.",[288,684,685],{},[45,686,687,688,417,691,693,694,697,698,700,701,417,703,705],{},"Isso é diferente de um ",[55,689,690],{},"slice",[55,692,61],{},", que aceita ",[55,695,696],{},"append"," numa boa — ponto que também já apareceu no artigo de zero values. A assimetria existe porque ",[55,699,696],{}," sempre pode criar um array novo por baixo, e o compilador sabe fazer isso. Escrever num ",[55,702,57],{},[55,704,61],{}," exigiria o mesmo truque, e o time do Go optou por não fazer alocação implícita ali; a spec faz questão de deixar isso explícito como exceção, não como acidente.",[45,707,708],{},"A correção de verdade é inicializar o campo no construtor:",[67,710,712],{"className":69,"code":711,"language":71,"meta":72,"style":72},"func NewRateLimiter() *RateLimiter {\n    return &RateLimiter{hits: make(map[string]int)}\n}\n",[55,713,714,731,766],{"__ignoreMap":72},[76,715,716,718,721,724,727,729],{"class":78,"line":79},[76,717,134],{"class":82},[76,719,720],{"class":153}," NewRateLimiter",[76,722,723],{"class":93},"()",[76,725,726],{"class":82}," *",[76,728,147],{"class":86},[76,730,94],{"class":93},[76,732,733,735,738,740,743,745,747,750,752,754,756,758,760,763],{"class":78,"line":97},[76,734,198],{"class":197},[76,736,737],{"class":82}," &",[76,739,147],{"class":86},[76,741,742],{"class":93},"{",[76,744,182],{"class":100},[76,746,359],{"class":93},[76,748,749],{"class":153}," make",[76,751,157],{"class":93},[76,753,57],{"class":82},[76,755,106],{"class":93},[76,757,110],{"class":109},[76,759,113],{"class":93},[76,761,762],{"class":109},"int",[76,764,765],{"class":93},")}\n",[76,767,768],{"class":78,"line":119},[76,769,122],{"class":93},[45,771,772,773,776,777,779,780,783,784,359],{},"Estruturas que vêm de fora, porém, às vezes te dão o ",[55,774,775],{},"make"," de graça. Um ",[55,778,57],{}," que é campo de destino de um ",[55,781,782],{},"json.Unmarshal"," é alocado automaticamente ",[294,785,786],{},"se a chave existir no JSON",[67,788,790],{"className":69,"code":789,"language":71,"meta":72,"style":72},"type Config struct {\n    Tags map[string]string\n}\n\nvar c Config\njson.Unmarshal([]byte(`{\"Tags\":{\"env\":\"prod\"}}`), &c)\nfmt.Println(c.Tags == nil) \u002F\u002F false\n\nvar c2 Config\njson.Unmarshal([]byte(`{}`), &c2)\nfmt.Println(c2.Tags == nil) \u002F\u002F true\n",[55,791,792,803,819,823,827,837,873,901,905,914,944],{"__ignoreMap":72},[76,793,794,796,799,801],{"class":78,"line":79},[76,795,83],{"class":82},[76,797,798],{"class":86}," Config",[76,800,90],{"class":82},[76,802,94],{"class":93},[76,804,805,808,810,812,814,816],{"class":78,"line":97},[76,806,807],{"class":100},"    Tags ",[76,809,57],{"class":82},[76,811,106],{"class":93},[76,813,110],{"class":109},[76,815,113],{"class":93},[76,817,818],{"class":109},"string\n",[76,820,821],{"class":78,"line":119},[76,822,122],{"class":93},[76,824,825],{"class":78,"line":125},[76,826,128],{"emptyLinePlaceholder":33},[76,828,829,831,834],{"class":78,"line":131},[76,830,239],{"class":82},[76,832,833],{"class":100}," c ",[76,835,836],{"class":86},"Config\n",[76,838,839,842,844,847,850,853,855,858,861,863,866,868,871],{"class":78,"line":173},[76,840,841],{"class":100},"json",[76,843,179],{"class":93},[76,845,846],{"class":153},"Unmarshal",[76,848,849],{"class":93},"([]",[76,851,852],{"class":109},"byte",[76,854,157],{"class":93},[76,856,857],{"class":260},"`",[76,859,860],{"class":264},"{\"Tags\":{\"env\":\"prod\"}}",[76,862,857],{"class":260},[76,864,865],{"class":93},"),",[76,867,737],{"class":82},[76,869,870],{"class":100},"c",[76,872,270],{"class":93},[76,874,875,877,879,881,883,885,887,890,892,896,898],{"class":78,"line":194},[76,876,461],{"class":100},[76,878,179],{"class":93},[76,880,466],{"class":153},[76,882,157],{"class":93},[76,884,870],{"class":100},[76,886,179],{"class":93},[76,888,889],{"class":100},"Tags ",[76,891,387],{"class":82},[76,893,895],{"class":894},"s39Yj"," nil",[76,897,150],{"class":93},[76,899,900],{"class":486}," \u002F\u002F false\n",[76,902,903],{"class":78,"line":221},[76,904,128],{"emptyLinePlaceholder":33},[76,906,907,909,912],{"class":78,"line":581},[76,908,239],{"class":82},[76,910,911],{"class":100}," c2 ",[76,913,836],{"class":86},[76,915,916,918,920,922,924,926,928,930,933,935,937,939,942],{"class":78,"line":586},[76,917,841],{"class":100},[76,919,179],{"class":93},[76,921,846],{"class":153},[76,923,849],{"class":93},[76,925,852],{"class":109},[76,927,157],{"class":93},[76,929,857],{"class":260},[76,931,932],{"class":264},"{}",[76,934,857],{"class":260},[76,936,865],{"class":93},[76,938,737],{"class":82},[76,940,941],{"class":100},"c2",[76,943,270],{"class":93},[76,945,947,949,951,953,955,957,959,961,963,965,967],{"class":78,"line":946},11,[76,948,461],{"class":100},[76,950,179],{"class":93},[76,952,466],{"class":153},[76,954,157],{"class":93},[76,956,941],{"class":100},[76,958,179],{"class":93},[76,960,889],{"class":100},[76,962,387],{"class":82},[76,964,895],{"class":894},[76,966,150],{"class":93},[76,968,969],{"class":486}," \u002F\u002F true\n",[45,971,972,973,975,976,979,980,983],{},"O que explica um bug clássico de configuração: o ",[55,974,57],{}," funciona em todo ambiente que define a chave ",[55,977,978],{},"Tags"," e panica especificamente naquele onde ela foi omitida. Não é um bug de ",[55,981,982],{},"encoding\u002Fjson",", é o comportamento documentado da spec aparecendo num lugar inesperado.",[985,986,987],"tip",{},[45,988,989,992,993,995,996,998,999,619,1001,406,1004,1007,1008,1011,1012,1015,1016,1018,1019,1022,1023,1025],{},[55,990,991],{},"clear(m)",", disponível desde o Go 1.21, esvazia um ",[55,994,57],{}," sem torná-lo ",[55,997,61],{},": depois de ",[55,1000,991],{},[55,1002,1003],{},"len(m)",[55,1005,1006],{},"0",", mas ",[55,1009,1010],{},"m == nil"," continua ",[55,1013,1014],{},"false",". Se o seu código depois checa ",[55,1017,1010],{}," para decidir se precisa inicializar, ",[55,1020,1021],{},"clear"," não serve para isso — é ",[55,1024,589],{}," de tudo, não é \"resetar para o zero value\".",[315,1027,1029],{"id":1028},"parte-2-a-ordem-de-iteração-é-aleatória-de-propósito","Parte 2: a ordem de iteração é aleatória de propósito",[45,1031,1032,1033,1035],{},"Aqui está uma demonstração simples: a mesma função, chamada cinco vezes seguidas, sobre o mesmo ",[55,1034,57],{},", no mesmo processo.",[67,1037,1039],{"className":69,"code":1038,"language":71,"meta":72,"style":72},"func keys(m map[int]bool) []int {\n    out := make([]int, 0, len(m))\n    for k := range m {\n        out = append(out, k)\n    }\n    return out\n}\n",[55,1040,1041,1073,1103,1119,1142,1147,1154],{"__ignoreMap":72},[76,1042,1043,1045,1048,1050,1052,1055,1057,1059,1061,1064,1066,1069,1071],{"class":78,"line":79},[76,1044,134],{"class":82},[76,1046,1047],{"class":153}," keys",[76,1049,157],{"class":93},[76,1051,471],{"class":140},[76,1053,1054],{"class":82}," map",[76,1056,106],{"class":93},[76,1058,762],{"class":109},[76,1060,113],{"class":93},[76,1062,1063],{"class":109},"bool",[76,1065,150],{"class":93},[76,1067,1068],{"class":93}," []",[76,1070,762],{"class":109},[76,1072,94],{"class":93},[76,1074,1075,1078,1080,1082,1084,1086,1088,1091,1093,1096,1098,1100],{"class":78,"line":97},[76,1076,1077],{"class":100},"    out ",[76,1079,524],{"class":82},[76,1081,749],{"class":153},[76,1083,849],{"class":93},[76,1085,762],{"class":109},[76,1087,518],{"class":93},[76,1089,1090],{"class":217}," 0",[76,1092,518],{"class":93},[76,1094,1095],{"class":153}," len",[76,1097,157],{"class":93},[76,1099,471],{"class":100},[76,1101,1102],{"class":93},"))\n",[76,1104,1105,1108,1111,1113,1115,1117],{"class":78,"line":119},[76,1106,1107],{"class":197},"    for",[76,1109,1110],{"class":100}," k ",[76,1112,524],{"class":82},[76,1114,568],{"class":197},[76,1116,442],{"class":100},[76,1118,573],{"class":93},[76,1120,1121,1124,1127,1130,1132,1135,1137,1140],{"class":78,"line":125},[76,1122,1123],{"class":100},"        out ",[76,1125,1126],{"class":82},"=",[76,1128,1129],{"class":153}," append",[76,1131,157],{"class":93},[76,1133,1134],{"class":100},"out",[76,1136,518],{"class":93},[76,1138,1139],{"class":100}," k",[76,1141,270],{"class":93},[76,1143,1144],{"class":78,"line":131},[76,1145,1146],{"class":93},"    }\n",[76,1148,1149,1151],{"class":78,"line":173},[76,1150,198],{"class":197},[76,1152,1153],{"class":100}," out\n",[76,1155,1156],{"class":78,"line":194},[76,1157,122],{"class":93},[67,1159,1162],{"className":1160,"code":1161,"language":276,"meta":72},[274],"[4 6 7 8 9 2 5 0 1 3]\n[1 3 4 6 7 8 9 2 5 0]\n[4 6 7 8 9 2 5 0 1 3]\n[0 1 3 4 6 7 8 9 2 5]\n[2 5 0 1 3 4 6 7 8 9]\n",[55,1163,1161],{"__ignoreMap":72},[45,1165,1166,1167,1169],{},"Cinco chamadas, quatro ordens diferentes, mesmo ",[55,1168,57],{},", mesmo processo, mesmos dados. A spec é explícita sobre isso desde a primeira versão pública da linguagem:",[326,1171,1172,1178],{},[45,1173,1174,1175,1177],{},"\"A ordem de iteração sobre maps não é especificada e não há garantia de que seja a mesma de uma iteração para a outra. Se uma entrada do map que ainda não foi alcançada é removida durante a iteração, o valor de iteração correspondente não será produzido. Se uma entrada é criada durante a iteração, ela pode ou não ser produzida durante aquela iteração. (...) Se o map é ",[55,1176,61],{},", o número de iterações é 0.\"",[45,1179,336,1180],{},[49,1181,342,1184],{"href":1182,"rel":1183},"https:\u002F\u002Fgo.dev\u002Fref\u002Fspec#For_statements",[341],[344,1185,1186],{},"For statements",[45,1188,1189,1190,1195],{},"Essa garantia (ou melhor, a ausência dela) já era documentada no ",[49,1191,1194],{"href":1192,"rel":1193},"https:\u002F\u002Fgo.dev\u002Fblog\u002Fmaps",[341],"Go Blog em 2013",", então não é novidade trazida pela Swiss Table de 2024: é uma escolha de design antiga.",[45,1197,1198,1199,1202,1203,1205],{},"Repare em algo nas cinco sequências acima: elas não são embaralhadas de forma independente, são ",[294,1200,1201],{},"rotações"," umas das outras. A segunda começa exatamente onde a primeira tinha \"1\" e continua na mesma ordem cíclica dali em diante. Isso não é coincidência, é o mecanismo: cada ",[55,1204,622],{}," sorteia só o ponto de partida, não a ordem relativa dos elementos.",[45,1207,1208],{},"A struct do iterador declara dois campos para isso:",[67,1210,1212],{"className":69,"code":1211,"language":71,"meta":72,"style":72},"\u002F\u002F Randomize iteration order by starting iteration at a random slot\n\u002F\u002F offset. The offset into the directory uses a separate offset, as it\n\u002F\u002F must adjust when the directory grows.\nentryOffset uint64\ndirOffset   uint64\n",[55,1213,1214,1219,1224,1229,1237],{"__ignoreMap":72},[76,1215,1216],{"class":78,"line":79},[76,1217,1218],{"class":486},"\u002F\u002F Randomize iteration order by starting iteration at a random slot\n",[76,1220,1221],{"class":78,"line":97},[76,1222,1223],{"class":486},"\u002F\u002F offset. The offset into the directory uses a separate offset, as it\n",[76,1225,1226],{"class":78,"line":119},[76,1227,1228],{"class":486},"\u002F\u002F must adjust when the directory grows.\n",[76,1230,1231,1234],{"class":78,"line":125},[76,1232,1233],{"class":100},"entryOffset ",[76,1235,1236],{"class":109},"uint64\n",[76,1238,1239,1242],{"class":78,"line":131},[76,1240,1241],{"class":100},"dirOffset   ",[76,1243,1236],{"class":109},[45,1245,1246,1247,359],{},"E o método que inicializa um novo iterador os preenche assim, uma vez por chamada de ",[55,1248,622],{},[67,1250,1252],{"className":69,"code":1251,"language":71,"meta":72,"style":72},"it.entryOffset = rand()\nit.dirOffset   = rand()\n",[55,1253,1254,1271],{"__ignoreMap":72},[76,1255,1256,1259,1261,1263,1265,1268],{"class":78,"line":79},[76,1257,1258],{"class":100},"it",[76,1260,179],{"class":93},[76,1262,1233],{"class":100},[76,1264,1126],{"class":82},[76,1266,1267],{"class":153}," rand",[76,1269,1270],{"class":93},"()\n",[76,1272,1273,1275,1277,1279,1281,1283],{"class":78,"line":97},[76,1274,1258],{"class":100},[76,1276,179],{"class":93},[76,1278,1241],{"class":100},[76,1280,1126],{"class":82},[76,1282,1267],{"class":153},[76,1284,1270],{"class":93},[45,1286,336,1287],{},[49,1288,1291],{"href":1289,"rel":1290},"https:\u002F\u002Fgithub.com\u002Fgolang\u002Fgo\u002Fblob\u002Fmaster\u002Fsrc\u002Finternal\u002Fruntime\u002Fmaps\u002Ftable.go",[341],[55,1292,1293],{},"internal\u002Fruntime\u002Fmaps\u002Ftable.go",[45,1295,1296,1297,1299],{},"Cada chamada a ",[55,1298,622],{}," cria um iterador novo, e esse iterador sorteia dois deslocamentos: um para a posição inicial dentro do grupo de slots, outro para o índice inicial no diretório de grupos. O layout interno dos dados não muda entre uma chamada e outra, só o ponto em que a varredura começa — e é exatamente por isso que a sequência observada é uma rotação, não uma permutação aleatória completa.",[1301,1302,1304],"h3",{"id":1303},"quando-você-precisa-de-ordem-peça-ordem","Quando você precisa de ordem, peça ordem",[45,1306,1307,1308,1310,1311,1314,1315,1318],{},"A saída correta para \"eu preciso imprimir isso sempre na mesma ordem\" não é torcer para o ",[55,1309,57],{}," cooperar, é ordenar as chaves explicitamente. Desde o Go 1.23, o pacote ",[55,1312,1313],{},"maps"," devolve um iterador, e o pacote ",[55,1316,1317],{},"slices"," sabe consumi-lo:",[67,1320,1322],{"className":69,"code":1321,"language":71,"meta":72,"style":72},"totals := map[string]int{\"checkout\": 42, \"home\": 108, \"login\": 17}\n\nkeys := slices.Sorted(maps.Keys(totals))\nfor _, k := range keys {\n    fmt.Printf(\"%s: %d\\n\", k, totals[k])\n}\n",[55,1323,1324,1385,1389,1420,1440,1487],{"__ignoreMap":72},[76,1325,1326,1329,1331,1333,1335,1337,1339,1341,1343,1345,1348,1350,1352,1355,1357,1359,1362,1364,1366,1369,1371,1373,1376,1378,1380,1383],{"class":78,"line":79},[76,1327,1328],{"class":100},"totals ",[76,1330,524],{"class":82},[76,1332,1054],{"class":82},[76,1334,106],{"class":93},[76,1336,110],{"class":109},[76,1338,113],{"class":93},[76,1340,762],{"class":109},[76,1342,742],{"class":93},[76,1344,261],{"class":260},[76,1346,1347],{"class":264},"checkout",[76,1349,261],{"class":260},[76,1351,359],{"class":93},[76,1353,1354],{"class":217}," 42",[76,1356,518],{"class":93},[76,1358,598],{"class":260},[76,1360,1361],{"class":264},"home",[76,1363,261],{"class":260},[76,1365,359],{"class":93},[76,1367,1368],{"class":217}," 108",[76,1370,518],{"class":93},[76,1372,598],{"class":260},[76,1374,1375],{"class":264},"login",[76,1377,261],{"class":260},[76,1379,359],{"class":93},[76,1381,1382],{"class":217}," 17",[76,1384,122],{"class":93},[76,1386,1387],{"class":78,"line":97},[76,1388,128],{"emptyLinePlaceholder":33},[76,1390,1391,1394,1396,1399,1401,1404,1406,1408,1410,1413,1415,1418],{"class":78,"line":119},[76,1392,1393],{"class":100},"keys ",[76,1395,524],{"class":82},[76,1397,1398],{"class":100}," slices",[76,1400,179],{"class":93},[76,1402,1403],{"class":153},"Sorted",[76,1405,157],{"class":93},[76,1407,1313],{"class":100},[76,1409,179],{"class":93},[76,1411,1412],{"class":153},"Keys",[76,1414,157],{"class":93},[76,1416,1417],{"class":100},"totals",[76,1419,1102],{"class":93},[76,1421,1422,1424,1427,1429,1431,1433,1435,1438],{"class":78,"line":125},[76,1423,565],{"class":197},[76,1425,1426],{"class":100}," _",[76,1428,518],{"class":93},[76,1430,1110],{"class":100},[76,1432,524],{"class":82},[76,1434,568],{"class":197},[76,1436,1437],{"class":100}," keys ",[76,1439,573],{"class":93},[76,1441,1442,1445,1447,1450,1452,1454,1458,1461,1464,1468,1470,1472,1474,1476,1479,1481,1484],{"class":78,"line":131},[76,1443,1444],{"class":100},"    fmt",[76,1446,179],{"class":93},[76,1448,1449],{"class":153},"Printf",[76,1451,157],{"class":93},[76,1453,261],{"class":260},[76,1455,1457],{"class":1456},"swQdS","%s",[76,1459,1460],{"class":264},": ",[76,1462,1463],{"class":1456},"%d",[76,1465,1467],{"class":1466},"s_hVV","\\n",[76,1469,261],{"class":260},[76,1471,518],{"class":93},[76,1473,1139],{"class":100},[76,1475,518],{"class":93},[76,1477,1478],{"class":100}," totals",[76,1480,106],{"class":93},[76,1482,1483],{"class":100},"k",[76,1485,1486],{"class":93},"])\n",[76,1488,1489],{"class":78,"line":173},[76,1490,122],{"class":93},[67,1492,1495],{"className":1493,"code":1494,"language":276,"meta":72},[274],"checkout: 42\nhome: 108\nlogin: 17\n",[55,1496,1494],{"__ignoreMap":72},[45,1498,1499,1500,1502,1503,1505],{},"Um lugar em que isso já acontece por baixo dos panos é ",[55,1501,982],{},". Serializar um ",[55,1504,57],{}," não devolve uma ordem aleatória:",[326,1507,1508,1514],{},[45,1509,1510,1511,1513],{},"\"Valores de ",[55,1512,57],{}," são codificados como objetos JSON. (...) As chaves do map são ordenadas e usadas como chaves do objeto JSON (...)\"",[45,1515,336,1516],{},[49,1517,1520,1521,619,1523],{"href":1518,"rel":1519},"https:\u002F\u002Fpkg.go.dev\u002Fencoding\u002Fjson#Marshal",[341],"Documentação do pacote ",[55,1522,982],{},[55,1524,1525],{},"Marshal",[67,1527,1529],{"className":69,"code":1528,"language":71,"meta":72,"style":72},"m := map[string]int{\"zebra\": 1, \"apple\": 2, \"mango\": 3}\nb, _ := json.Marshal(m)\nfmt.Println(string(b))\n",[55,1530,1531,1592,1617],{"__ignoreMap":72},[76,1532,1533,1536,1538,1540,1542,1544,1546,1548,1550,1552,1555,1557,1559,1562,1564,1566,1569,1571,1573,1576,1578,1580,1583,1585,1587,1590],{"class":78,"line":79},[76,1534,1535],{"class":100},"m ",[76,1537,524],{"class":82},[76,1539,1054],{"class":82},[76,1541,106],{"class":93},[76,1543,110],{"class":109},[76,1545,113],{"class":93},[76,1547,762],{"class":109},[76,1549,742],{"class":93},[76,1551,261],{"class":260},[76,1553,1554],{"class":264},"zebra",[76,1556,261],{"class":260},[76,1558,359],{"class":93},[76,1560,1561],{"class":217}," 1",[76,1563,518],{"class":93},[76,1565,598],{"class":260},[76,1567,1568],{"class":264},"apple",[76,1570,261],{"class":260},[76,1572,359],{"class":93},[76,1574,1575],{"class":217}," 2",[76,1577,518],{"class":93},[76,1579,598],{"class":260},[76,1581,1582],{"class":264},"mango",[76,1584,261],{"class":260},[76,1586,359],{"class":93},[76,1588,1589],{"class":217}," 3",[76,1591,122],{"class":93},[76,1593,1594,1597,1599,1602,1604,1607,1609,1611,1613,1615],{"class":78,"line":97},[76,1595,1596],{"class":100},"b",[76,1598,518],{"class":93},[76,1600,1601],{"class":100}," _ ",[76,1603,524],{"class":82},[76,1605,1606],{"class":100}," json",[76,1608,179],{"class":93},[76,1610,1525],{"class":153},[76,1612,157],{"class":93},[76,1614,471],{"class":100},[76,1616,270],{"class":93},[76,1618,1619,1621,1623,1625,1627,1629,1631,1633],{"class":78,"line":119},[76,1620,461],{"class":100},[76,1622,179],{"class":93},[76,1624,466],{"class":153},[76,1626,157],{"class":93},[76,1628,110],{"class":109},[76,1630,157],{"class":93},[76,1632,1596],{"class":100},[76,1634,1102],{"class":93},[67,1636,1639],{"className":1637,"code":1638,"language":276,"meta":72},[274],"{\"apple\":2,\"mango\":3,\"zebra\":1}\n",[55,1640,1638],{"__ignoreMap":72},[45,1642,1643,1644,1646,1647,1649],{},"Rodei esse ",[55,1645,1525],{}," três vezes no mesmo processo: as três saídas vieram idênticas, em ordem alfabética. ",[55,1648,982],{}," ordena as chaves para você antes de serializar, exatamente para que o resultado seja determinístico apesar da iteração por baixo não ser.",[985,1651,1652],{},[45,1653,1654,1655,417,1657,1660,1661,1664,1665,1668,1669,1672,1673,1676],{},"Essa garantia é do pacote ",[55,1656,982],{},[294,1658,1659],{},"v1",", o que você importa com ",[55,1662,1663],{},"\"encoding\u002Fjson\"",". Existe um pacote experimental ",[55,1666,1667],{},"encoding\u002Fjson\u002Fv2"," (atrás da flag ",[55,1670,1671],{},"GOEXPERIMENT=jsonv2",", ainda não habilitado por padrão no Go 1.26) cuja documentação declara explicitamente o oposto por padrão: ordem não determinística, com uma opção ",[55,1674,1675],{},"jsonv2.Deterministic"," para religar o comportamento antigo. Se esse pacote se tornar padrão no futuro, vale reconferir esse ponto.",[315,1678,1680],{"id":1679},"parte-3-concorrência-o-que-é-seguro-e-o-que-não-é","Parte 3: concorrência — o que é seguro e o que não é",[45,1682,1683],{},"A pergunta que o time do Go já respondeu publicamente, e vale citar por inteiro porque a resposta é mais matizada do que \"use um mutex\":",[326,1685,1686,1693],{},[45,1687,1688,1689,1692],{},"\"Depois de uma longa discussão, decidiu-se que o uso típico de maps não exigia acesso seguro a partir de múltiplas goroutines, e nos casos em que exigia, o map provavelmente fazia parte de uma estrutura de dados ou computação maior que já estava sincronizada. (...) O acesso a um map só é inseguro quando há atualizações ocorrendo. Enquanto todas as goroutines estiverem apenas lendo — buscando elementos no map, inclusive iterando com um ",[55,1690,1691],{},"for range"," — e não alterando o map por atribuição ou remoção, é seguro acessá-lo concorrentemente sem sincronização.\"",[45,1694,336,1695],{},[49,1696,1699,1700],{"href":1697,"rel":1698},"https:\u002F\u002Fgo.dev\u002Fdoc\u002Ffaq#atomic_maps",[341],"Go FAQ, ",[344,1701,1702],{},"Why are map operations not defined to be atomic?",[45,1704,1705,1706,1709],{},"Ou seja: ",[294,1707,1708],{},"leituras concorrentes são seguras, desde que não exista um escritor",". No momento em que existe uma escrita concorrente com qualquer outro acesso (leitura ou escrita), a garantia acaba.",[45,1711,1712],{},"Um cenário realista: um contador de acessos por rota, incrementado a cada request e lido por um endpoint de métricas.",[67,1714,1716],{"className":69,"code":1715,"language":71,"meta":72,"style":72},"hits := map[string]int{\"home\": 0, \"login\": 0, \"checkout\": 0}\n\ngo func() { \u002F\u002F simula requests incrementando\n    for i := 0; i \u003C 1_000_000; i++ {\n        hits[\"home\"]++\n    }\n}()\n\ngo func() { \u002F\u002F simula o endpoint de métricas lendo\n    for i := 0; i \u003C 1_000_000; i++ {\n        _ = hits[\"home\"]\n    }\n}()\n",[55,1717,1718,1773,1777,1792,1833,1850,1854,1859,1863,1876,1910,1930,1935],{"__ignoreMap":72},[76,1719,1720,1723,1725,1727,1729,1731,1733,1735,1737,1739,1741,1743,1745,1747,1749,1751,1753,1755,1757,1759,1761,1763,1765,1767,1769,1771],{"class":78,"line":79},[76,1721,1722],{"class":100},"hits ",[76,1724,524],{"class":82},[76,1726,1054],{"class":82},[76,1728,106],{"class":93},[76,1730,110],{"class":109},[76,1732,113],{"class":93},[76,1734,762],{"class":109},[76,1736,742],{"class":93},[76,1738,261],{"class":260},[76,1740,1361],{"class":264},[76,1742,261],{"class":260},[76,1744,359],{"class":93},[76,1746,1090],{"class":217},[76,1748,518],{"class":93},[76,1750,598],{"class":260},[76,1752,1375],{"class":264},[76,1754,261],{"class":260},[76,1756,359],{"class":93},[76,1758,1090],{"class":217},[76,1760,518],{"class":93},[76,1762,598],{"class":260},[76,1764,1347],{"class":264},[76,1766,261],{"class":260},[76,1768,359],{"class":93},[76,1770,1090],{"class":217},[76,1772,122],{"class":93},[76,1774,1775],{"class":78,"line":97},[76,1776,128],{"emptyLinePlaceholder":33},[76,1778,1779,1781,1784,1786,1789],{"class":78,"line":119},[76,1780,71],{"class":197},[76,1782,1783],{"class":82}," func",[76,1785,723],{"class":93},[76,1787,1788],{"class":93}," {",[76,1790,1791],{"class":486}," \u002F\u002F simula requests incrementando\n",[76,1793,1794,1796,1799,1801,1803,1806,1808,1811,1813,1816,1819,1821,1823,1825,1828,1831],{"class":78,"line":125},[76,1795,1107],{"class":197},[76,1797,1798],{"class":100}," i ",[76,1800,524],{"class":82},[76,1802,1090],{"class":217},[76,1804,1805],{"class":93},";",[76,1807,1798],{"class":100},[76,1809,1810],{"class":82},"\u003C",[76,1812,1561],{"class":217},[76,1814,1815],{"class":894},"_",[76,1817,1818],{"class":217},"000",[76,1820,1815],{"class":894},[76,1822,1818],{"class":217},[76,1824,1805],{"class":93},[76,1826,1827],{"class":100}," i",[76,1829,1830],{"class":82},"++",[76,1832,94],{"class":93},[76,1834,1835,1838,1840,1842,1844,1846,1848],{"class":78,"line":131},[76,1836,1837],{"class":100},"        hits",[76,1839,106],{"class":93},[76,1841,261],{"class":260},[76,1843,1361],{"class":264},[76,1845,261],{"class":260},[76,1847,113],{"class":93},[76,1849,191],{"class":82},[76,1851,1852],{"class":78,"line":173},[76,1853,1146],{"class":93},[76,1855,1856],{"class":78,"line":194},[76,1857,1858],{"class":93},"}()\n",[76,1860,1861],{"class":78,"line":221},[76,1862,128],{"emptyLinePlaceholder":33},[76,1864,1865,1867,1869,1871,1873],{"class":78,"line":581},[76,1866,71],{"class":197},[76,1868,1783],{"class":82},[76,1870,723],{"class":93},[76,1872,1788],{"class":93},[76,1874,1875],{"class":486}," \u002F\u002F simula o endpoint de métricas lendo\n",[76,1877,1878,1880,1882,1884,1886,1888,1890,1892,1894,1896,1898,1900,1902,1904,1906,1908],{"class":78,"line":586},[76,1879,1107],{"class":197},[76,1881,1798],{"class":100},[76,1883,524],{"class":82},[76,1885,1090],{"class":217},[76,1887,1805],{"class":93},[76,1889,1798],{"class":100},[76,1891,1810],{"class":82},[76,1893,1561],{"class":217},[76,1895,1815],{"class":894},[76,1897,1818],{"class":217},[76,1899,1815],{"class":894},[76,1901,1818],{"class":217},[76,1903,1805],{"class":93},[76,1905,1827],{"class":100},[76,1907,1830],{"class":82},[76,1909,94],{"class":93},[76,1911,1912,1915,1917,1920,1922,1924,1926,1928],{"class":78,"line":946},[76,1913,1914],{"class":100},"        _ ",[76,1916,1126],{"class":82},[76,1918,1919],{"class":100}," hits",[76,1921,106],{"class":93},[76,1923,261],{"class":260},[76,1925,1361],{"class":264},[76,1927,261],{"class":260},[76,1929,538],{"class":93},[76,1931,1933],{"class":78,"line":1932},12,[76,1934,1146],{"class":93},[76,1936,1938],{"class":78,"line":1937},13,[76,1939,1858],{"class":93},[67,1941,1944],{"className":1942,"code":1943,"language":276,"meta":72},[274],"fatal error: concurrent map read and map write\n",[55,1945,1943],{"__ignoreMap":72},[45,1947,1948,1949,1952],{},"Rodei esse programa várias vezes, sem a flag ",[55,1950,1951],{},"-race",", e ele derrubou o processo de forma consistente com essa mensagem. Se as duas goroutines escrevem (em vez de uma ler e outra escrever), a mensagem muda:",[67,1954,1957],{"className":1955,"code":1956,"language":276,"meta":72},[274],"fatal error: concurrent map writes\n",[55,1958,1956],{"__ignoreMap":72},[288,1960,1961],{},[45,1962,1963,1964,1967,1968,1971,1972,1975,1976,1979,1980,1983,1984,1986],{},"Tentei envolver esse programa num ",[55,1965,1966],{},"recover()",". Não adiantou — o processo derruba do mesmo jeito. A razão é que isso não é um ",[55,1969,1970],{},"panic",": é um ",[55,1973,1974],{},"fatal error",", gerado pela função interna ",[55,1977,1978],{},"fatal()"," do runtime, que existe justamente para condições que o runtime considera não seguras de continuar, mesmo que alguém tenha um ",[55,1981,1982],{},"recover"," no meio do caminho. ",[55,1985,1970],{}," você pode interceptar; isso, não.",[45,1988,1989],{},"O runtime detecta esses casos com uma verificação leve, presente desde o Go 1.6:",[326,1991,1992,1995],{},[45,1993,1994],{},"\"O runtime passou a ter uma detecção leve e best-effort de uso concorrente incorreto de maps. Como sempre, se uma goroutine está escrevendo em um map, nenhuma outra goroutine deveria estar lendo ou escrevendo nesse map concorrentemente. Se o runtime detecta essa condição, ele imprime um diagnóstico e derruba o programa.\"",[45,1996,336,1997],{},[49,1998,2001],{"href":1999,"rel":2000},"https:\u002F\u002Fgo.dev\u002Fdoc\u002Fgo1.6",[341],"Go 1.6 Release Notes",[45,2003,2004,2005,2008,2009,2012,2013,2016,2017,2020,2021,1460,2026,619,2029,179],{},"Por dentro, a implementação mantém um campo (",[55,2006,2007],{},"writing",", em ",[55,2010,2011],{},"internal\u002Fruntime\u002Fmaps\u002Fmap.go",") que é alternado a cada escrita; se uma leitura ou outra escrita encontra esse campo já ligado, ela chama ",[55,2014,2015],{},"fatal",". É uma verificação leve de propósito — ",[294,2018,2019],{},"best-effort",", nas palavras da release note — não uma trava. Ela pega grande parte dos casos reais, mas não é uma prova de ausência de corrida: código que passa sem crashar não está necessariamente correto, só não teve azar naquela execução. Para uma detecção confiável, a ferramenta certa é o ",[49,2022,2025],{"href":2023,"rel":2024},"https:\u002F\u002Fgo.dev\u002Fblog\u002Frace-detector",[341],"detector de corrida",[55,2027,2028],{},"go test -race",[55,2030,2031],{},"go run -race",[1301,2033,2035],{"id":2034},"duas-ferramentas-dois-problemas-diferentes","Duas ferramentas, dois problemas diferentes",[45,2037,2038,2039,2042],{},"A resposta para \"qual mecanismo eu uso\" não é sempre a mesma. A documentação do ",[55,2040,2041],{},"sync.Map"," é direta sobre isso:",[326,2044,2045,2052],{},[45,2046,2047,2048,2051],{},"\"O tipo ",[55,2049,2050],{},"Map"," é especializado. A maior parte do código deveria usar um map comum do Go, com locking separado, para melhor segurança de tipos e para facilitar a manutenção de outras invariantes junto com o conteúdo do map.\"",[45,2053,336,2054],{},[49,2055,1520,2058,619,2061],{"href":2056,"rel":2057},"https:\u002F\u002Fpkg.go.dev\u002Fsync#Map",[341],[55,2059,2060],{},"sync",[55,2062,2050],{},[45,2064,2065,2066,2068,2069,2072,2073,359],{},"O caso padrão, então, é ",[55,2067,57],{}," comum protegido por ",[55,2070,2071],{},"sync.Mutex"," ou ",[55,2074,2075],{},"sync.RWMutex",[67,2077,2079],{"className":69,"code":2078,"language":71,"meta":72,"style":72},"type Cache struct {\n    mu sync.RWMutex\n    m  map[string]int\n}\n\nfunc (c *Cache) Get(k string) (int, bool) {\n    c.mu.RLock()\n    defer c.mu.RUnlock()\n    v, ok := c.m[k]\n    return v, ok\n}\n\nfunc (c *Cache) Set(k string, v int) {\n    c.mu.Lock()\n    defer c.mu.Unlock()\n    c.m[k] = v\n}\n",[55,2080,2081,2092,2104,2119,2123,2127,2166,2183,2202,2225,2237,2241,2245,2279,2295,2313,2333],{"__ignoreMap":72},[76,2082,2083,2085,2088,2090],{"class":78,"line":79},[76,2084,83],{"class":82},[76,2086,2087],{"class":86}," Cache",[76,2089,90],{"class":82},[76,2091,94],{"class":93},[76,2093,2094,2097,2099,2101],{"class":78,"line":97},[76,2095,2096],{"class":100},"    mu ",[76,2098,2060],{"class":86},[76,2100,179],{"class":93},[76,2102,2103],{"class":86},"RWMutex\n",[76,2105,2106,2109,2111,2113,2115,2117],{"class":78,"line":119},[76,2107,2108],{"class":100},"    m  ",[76,2110,57],{"class":82},[76,2112,106],{"class":93},[76,2114,110],{"class":109},[76,2116,113],{"class":93},[76,2118,116],{"class":109},[76,2120,2121],{"class":78,"line":125},[76,2122,122],{"class":93},[76,2124,2125],{"class":78,"line":131},[76,2126,128],{"emptyLinePlaceholder":33},[76,2128,2129,2131,2133,2136,2138,2141,2143,2146,2148,2150,2152,2154,2156,2158,2160,2162,2164],{"class":78,"line":173},[76,2130,134],{"class":82},[76,2132,137],{"class":93},[76,2134,2135],{"class":140},"c ",[76,2137,144],{"class":82},[76,2139,2140],{"class":86},"Cache",[76,2142,150],{"class":93},[76,2144,2145],{"class":153}," Get",[76,2147,157],{"class":93},[76,2149,1483],{"class":140},[76,2151,163],{"class":109},[76,2153,150],{"class":93},[76,2155,137],{"class":93},[76,2157,762],{"class":109},[76,2159,518],{"class":93},[76,2161,168],{"class":109},[76,2163,150],{"class":93},[76,2165,94],{"class":93},[76,2167,2168,2171,2173,2176,2178,2181],{"class":78,"line":194},[76,2169,2170],{"class":100},"    c",[76,2172,179],{"class":93},[76,2174,2175],{"class":100},"mu",[76,2177,179],{"class":93},[76,2179,2180],{"class":153},"RLock",[76,2182,1270],{"class":93},[76,2184,2185,2188,2191,2193,2195,2197,2200],{"class":78,"line":221},[76,2186,2187],{"class":197},"    defer",[76,2189,2190],{"class":100}," c",[76,2192,179],{"class":93},[76,2194,2175],{"class":100},[76,2196,179],{"class":93},[76,2198,2199],{"class":153},"RUnlock",[76,2201,1270],{"class":93},[76,2203,2204,2207,2209,2211,2213,2215,2217,2219,2221,2223],{"class":78,"line":581},[76,2205,2206],{"class":100},"    v",[76,2208,518],{"class":93},[76,2210,521],{"class":100},[76,2212,524],{"class":82},[76,2214,2190],{"class":100},[76,2216,179],{"class":93},[76,2218,471],{"class":100},[76,2220,106],{"class":93},[76,2222,1483],{"class":100},[76,2224,538],{"class":93},[76,2226,2227,2229,2232,2234],{"class":78,"line":586},[76,2228,198],{"class":197},[76,2230,2231],{"class":100}," v",[76,2233,518],{"class":93},[76,2235,2236],{"class":100}," ok\n",[76,2238,2239],{"class":78,"line":946},[76,2240,122],{"class":93},[76,2242,2243],{"class":78,"line":1932},[76,2244,128],{"emptyLinePlaceholder":33},[76,2246,2247,2249,2251,2253,2255,2257,2259,2262,2264,2266,2268,2270,2272,2275,2277],{"class":78,"line":1937},[76,2248,134],{"class":82},[76,2250,137],{"class":93},[76,2252,2135],{"class":140},[76,2254,144],{"class":82},[76,2256,2140],{"class":86},[76,2258,150],{"class":93},[76,2260,2261],{"class":153}," Set",[76,2263,157],{"class":93},[76,2265,1483],{"class":140},[76,2267,163],{"class":109},[76,2269,518],{"class":93},[76,2271,2231],{"class":140},[76,2273,2274],{"class":109}," int",[76,2276,150],{"class":93},[76,2278,94],{"class":93},[76,2280,2282,2284,2286,2288,2290,2293],{"class":78,"line":2281},14,[76,2283,2170],{"class":100},[76,2285,179],{"class":93},[76,2287,2175],{"class":100},[76,2289,179],{"class":93},[76,2291,2292],{"class":153},"Lock",[76,2294,1270],{"class":93},[76,2296,2298,2300,2302,2304,2306,2308,2311],{"class":78,"line":2297},15,[76,2299,2187],{"class":197},[76,2301,2190],{"class":100},[76,2303,179],{"class":93},[76,2305,2175],{"class":100},[76,2307,179],{"class":93},[76,2309,2310],{"class":153},"Unlock",[76,2312,1270],{"class":93},[76,2314,2316,2318,2320,2322,2324,2326,2328,2330],{"class":78,"line":2315},16,[76,2317,2170],{"class":100},[76,2319,179],{"class":93},[76,2321,471],{"class":100},[76,2323,106],{"class":93},[76,2325,1483],{"class":100},[76,2327,113],{"class":93},[76,2329,664],{"class":82},[76,2331,2332],{"class":100}," v\n",[76,2334,2336],{"class":78,"line":2335},17,[76,2337,122],{"class":93},[45,2339,2340,2342],{},[55,2341,2041],{}," existe para dois padrões de acesso específicos, também descritos na documentação:",[326,2344,2345,2358],{},[45,2346,2347,2348,2350,2351,2072,2354,2357],{},"\"quando a entrada de uma dada chave é escrita apenas uma vez mas lida muitas vezes, como em caches que só crescem\" (...) \"quando múltiplas goroutines leem, escrevem e sobrescrevem entradas para conjuntos disjuntos de chaves (...) Nesses dois casos, o uso de um ",[55,2349,2050],{}," pode reduzir significativamente a contenção de locks em comparação com um map do Go pareado com um ",[55,2352,2353],{},"Mutex",[55,2355,2356],{},"RWMutex"," separado.\"",[45,2359,336,2360],{},[49,2361,1520,2363,619,2365],{"href":2056,"rel":2362},[341],[55,2364,2060],{},[55,2366,2050],{},[45,2368,2369],{},"Medindo exatamente o primeiro cenário — popular o cache uma vez, depois bombardear com leituras concorrentes de 8 goroutines:",[67,2371,2374],{"className":2372,"code":2373,"language":276,"meta":72},[274],"BenchmarkMutexCache-8   23049524    52.67 ns\u002Fop    0 B\u002Fop   0 allocs\u002Fop\nBenchmarkSyncMap-8     185469519     7.193 ns\u002Fop    0 B\u002Fop   0 allocs\u002Fop\n",[55,2375,2373],{"__ignoreMap":72},[45,2377,2378,2380,2381,2383,2384,2386,2387,2390],{},[55,2379,2041],{}," é cerca de 7× mais rápido aqui, e a razão é estrutural. Desde o Go 1.24, ",[55,2382,2041],{}," não é mais implementado com o desenho clássico de \"read map somado a dirty map protegida por ",[55,2385,2353],{},"\": por baixo ele usa uma hash-trie concorrente (",[55,2388,2389],{},"internal\u002Fsync.HashTrieMap","), que a documentação descreve como \"desenhada para leituras frequentes, mas com desempenho razoável também para escritas e remoções\". A release note é direta sobre o ganho:",[326,2392,2393,2399],{},[45,2394,2395,2396,2398],{},"\"A implementação de ",[55,2397,2041],{}," mudou, melhorando a performance, particularmente para modificações no map. Por exemplo, modificações em conjuntos disjuntos de chaves têm muito menos chance de disputa em maps grandes, e não existe mais nenhum tempo de aquecimento necessário para atingir baixa contenção em leituras do map.\"",[45,2400,336,2401],{},[49,2402,374],{"href":372,"rel":2403},[341],[45,2405,2406,2407,2409,2410,2412,2413,2415,2416,2418],{},"Fora do padrão \"escreve uma vez, lê muitas vezes\" (ou do padrão de chaves disjuntas), essa vantagem desaparece ou inverte, porque ",[55,2408,2041],{}," ainda paga um custo de indireção que o ",[55,2411,57],{},"+",[55,2414,2353],{}," não tem. É por isso que a documentação não recomenda ",[55,2417,2041],{}," como padrão: ela ajuda exatamente dois formatos de acesso, e atrapalha fora deles.",[315,2420,2422],{"id":2421},"limitações-exceções-e-armadilhas","Limitações, exceções e armadilhas",[45,2424,2425],{},[294,2426,2427,2428,2430],{},"Você não pode atribuir a um campo de struct dentro de um ",[55,2429,57],{}," diretamente.",[67,2432,2434],{"className":69,"code":2433,"language":71,"meta":72,"style":72},"type Order struct{ Status string }\n\nm := map[string]Order{\"a\": {Status: \"pending\"}}\nm[\"a\"].Status = \"paid\"\n",[55,2435,2436,2455,2459,2503],{"__ignoreMap":72},[76,2437,2438,2440,2443,2445,2447,2450,2452],{"class":78,"line":79},[76,2439,83],{"class":82},[76,2441,2442],{"class":86}," Order",[76,2444,90],{"class":82},[76,2446,742],{"class":93},[76,2448,2449],{"class":100}," Status ",[76,2451,110],{"class":109},[76,2453,2454],{"class":93}," }\n",[76,2456,2457],{"class":78,"line":97},[76,2458,128],{"emptyLinePlaceholder":33},[76,2460,2461,2463,2465,2467,2469,2471,2473,2476,2478,2480,2482,2484,2486,2488,2491,2493,2495,2498,2500],{"class":78,"line":119},[76,2462,1535],{"class":100},[76,2464,524],{"class":82},[76,2466,1054],{"class":82},[76,2468,106],{"class":93},[76,2470,110],{"class":109},[76,2472,113],{"class":93},[76,2474,2475],{"class":86},"Order",[76,2477,742],{"class":93},[76,2479,261],{"class":260},[76,2481,49],{"class":264},[76,2483,261],{"class":260},[76,2485,359],{"class":93},[76,2487,1788],{"class":93},[76,2489,2490],{"class":100},"Status",[76,2492,359],{"class":93},[76,2494,598],{"class":260},[76,2496,2497],{"class":264},"pending",[76,2499,261],{"class":260},[76,2501,2502],{"class":93},"}}\n",[76,2504,2505,2507,2509,2511,2513,2515,2518,2521,2523,2525,2528],{"class":78,"line":125},[76,2506,471],{"class":100},[76,2508,106],{"class":93},[76,2510,261],{"class":260},[76,2512,49],{"class":264},[76,2514,261],{"class":260},[76,2516,2517],{"class":93},"].",[76,2519,2520],{"class":100},"Status ",[76,2522,1126],{"class":82},[76,2524,598],{"class":260},[76,2526,2527],{"class":264},"paid",[76,2529,2530],{"class":260},"\"\n",[67,2532,2535],{"className":2533,"code":2534,"language":276,"meta":72},[274],"cannot assign to struct field m[\"a\"].Status in map\n",[55,2536,2534],{"__ignoreMap":72},[45,2538,2539,2540,2543,2544,2546,2547,2550,2551,2554],{},"O motivo é que ",[55,2541,2542],{},"m[\"a\"]"," não é endereçável: cada acesso de leitura devolve uma cópia do valor guardado, não um ponteiro para dentro do ",[55,2545,57],{},". A saída é ler, alterar a cópia e regravar (",[55,2548,2549],{},"o := m[\"a\"]; o.Status = \"paid\"; m[\"a\"] = o","), ou guardar ",[55,2552,2553],{},"map[string]*Order"," desde o início.",[45,2556,2557,2560,2561,2564,2565,2567,2568,2570,2571,2574,2575,2577,2578,2580,2581,2072,2584,2587,2588,2590],{},[294,2558,2559],{},"Maps não encolhem sozinhos."," Depois de remover 2 milhões de entradas de um ",[55,2562,2563],{},"map[int][32]byte",", seja com ",[55,2566,589],{}," em loop, seja com ",[55,2569,991],{},", o heap continuou no mesmo patamar de antes da remoção (192 MiB, medido com ",[55,2572,2573],{},"runtime.ReadMemStats",", antes e depois). O ",[55,2576,57],{}," mantém a tabela interna no tamanho do pico de uso; ela só é liberada quando o ",[55,2579,57],{}," inteiro deixa de ser referenciado (",[55,2582,2583],{},"m = make(map[K]V)",[55,2585,2586],{},"m = nil",") e o GC recolhe a estrutura antiga. Se um ",[55,2589,57],{}," de vida longa passa por um pico e depois fica pequeno, considerar recriá-lo é uma opção real, não paranoia.",[45,2592,2593,2599,2600,2602,2603,2605],{},[294,2594,2595,2596,2598],{},"A detecção de concorrência é probabilística, o ",[55,2597,1951],{}," não."," Um programa que roda sem ",[55,2601,1974],{}," várias vezes seguidas não está provado correto — a chance de duas goroutines colidirem no exato instante que o runtime verifica depende de timing. CI que quer pegar isso de verdade precisa rodar com ",[55,2604,1951],{},", não só observar ausência de crash.",[45,2607,2608,2613],{},[294,2609,2610,2612],{},[55,2611,61],{}," map e map vazio não são a mesma coisa na borda com JSON",", no mesmo espírito do que já valia para slices:",[67,2615,2617],{"className":69,"code":2616,"language":71,"meta":72,"style":72},"var nilMap map[string]int\nemptyMap := map[string]int{}\n\njson.Marshal(nilMap)   \u002F\u002F null\njson.Marshal(emptyMap) \u002F\u002F {}\n",[55,2618,2619,2636,2656,2660,2678],{"__ignoreMap":72},[76,2620,2621,2623,2626,2628,2630,2632,2634],{"class":78,"line":79},[76,2622,239],{"class":82},[76,2624,2625],{"class":100}," nilMap ",[76,2627,57],{"class":82},[76,2629,106],{"class":93},[76,2631,110],{"class":109},[76,2633,113],{"class":93},[76,2635,116],{"class":109},[76,2637,2638,2641,2643,2645,2647,2649,2651,2653],{"class":78,"line":97},[76,2639,2640],{"class":100},"emptyMap ",[76,2642,524],{"class":82},[76,2644,1054],{"class":82},[76,2646,106],{"class":93},[76,2648,110],{"class":109},[76,2650,113],{"class":93},[76,2652,762],{"class":109},[76,2654,2655],{"class":93},"{}\n",[76,2657,2658],{"class":78,"line":119},[76,2659,128],{"emptyLinePlaceholder":33},[76,2661,2662,2664,2666,2668,2670,2673,2675],{"class":78,"line":125},[76,2663,841],{"class":100},[76,2665,179],{"class":93},[76,2667,1525],{"class":153},[76,2669,157],{"class":93},[76,2671,2672],{"class":100},"nilMap",[76,2674,150],{"class":93},[76,2676,2677],{"class":486},"   \u002F\u002F null\n",[76,2679,2680,2682,2684,2686,2688,2691,2693],{"class":78,"line":131},[76,2681,841],{"class":100},[76,2683,179],{"class":93},[76,2685,1525],{"class":153},[76,2687,157],{"class":93},[76,2689,2690],{"class":100},"emptyMap",[76,2692,150],{"class":93},[76,2694,2695],{"class":486}," \u002F\u002F {}\n",[45,2697,2698,2699,2701,2702,2705,2706,2708],{},"Se a sua API devolve um objeto e o contrato promete ",[55,2700,932],{}," em vez de ",[55,2703,2704],{},"null",", garanta que o ",[55,2707,57],{}," foi inicializado antes de serializar.",[45,2710,2711,2716,2717,2719,2720,2722,2723,2725,2726,2728],{},[294,2712,2713,2715],{},[55,2714,2041],{}," não é gratuito fora do seu nicho."," A própria documentação cita free lists de objetos de vida curta como um exemplo de uso ruim: \"o overhead não se amortiza bem nesse cenário\". Fora dos dois padrões documentados, comece com ",[55,2718,57],{}," + ",[55,2721,2353],{},"\u002F",[55,2724,2356],{}," e só troque se um profile mostrar contenção de lock que ",[55,2727,2041],{}," resolveria.",[315,2730,2732],{"id":2731},"mudanças-entre-versões","Mudanças entre versões",[2734,2735,2736,2749],"table",{},[2737,2738,2739],"thead",{},[2740,2741,2742,2746],"tr",{},[2743,2744,2745],"th",{},"Versão",[2743,2747,2748],{},"O que mudou",[2750,2751,2752,2764,2784,2816,2835],"tbody",{},[2740,2753,2754,2758],{},[2755,2756,2757],"td",{},"Go 1.6",[2755,2759,2760,2761,2763],{},"Detecção leve e best-effort de uso concorrente incorreto de maps, com ",[55,2762,1974],{}," diagnosticando a condição.",[2740,2765,2766,2769],{},[2755,2767,2768],{},"Go 1.21",[2755,2770,2771,2772,2774,2775,2777,2778,2780,2781,2783],{},"Builtin ",[55,2773,1021],{}," (esvazia um map sem realocar e sem torná-lo ",[55,2776,61],{},"); pacotes ",[55,2779,1317],{}," e ",[55,2782,1313],{}," chegam à biblioteca padrão.",[2740,2785,2786,2789],{},[2755,2787,2788],{},"Go 1.23",[2755,2790,2791,2792,2795,2796,619,2799,2780,2802,2805,2806,2795,2809,2780,2812,2815],{},"Iteradores ",[55,2793,2794],{},"range-over-func","; ",[55,2797,2798],{},"maps.Keys",[55,2800,2801],{},"maps.Values",[55,2803,2804],{},"maps.All"," passam a devolver ",[55,2807,2808],{},"iter.Seq",[55,2810,2811],{},"slices.Sorted",[55,2813,2814],{},"slices.Collect"," chegam para consumi-los.",[2740,2817,2818,2820],{},[2755,2819,352],{},[2755,2821,2822,2823,2825,2826,2828,2829,2831,2832,179],{},"Nova implementação de ",[55,2824,57],{}," baseada em Swiss Tables, reversível via ",[55,2827,380],{},". Redução média de 2–3% no overhead de CPU do runtime; nenhuma mudança de semântica. ",[55,2830,2041],{}," também foi reimplementado sobre uma hash-trie concorrente, reversível via ",[55,2833,2834],{},"GOEXPERIMENT=nosynchashtriemap",[2740,2836,2837,2840],{},[2755,2838,2839],{},"Go 1.25+ (experimental)",[2755,2841,2842,2843,2845,2846,2848,2849,2851,2852,2854],{},"Pacote ",[55,2844,1667],{},", atrás de ",[55,2847,1671],{},", inverte o padrão de serialização de maps para ordem não determinística, com a opção ",[55,2850,1675],{}," para restaurar o comportamento de v1. Ainda não é o ",[55,2853,982],{}," padrão.",[315,2856,2858],{"id":2857},"juntando-tudo-o-caminho-mental","Juntando tudo: o caminho mental",[45,2860,2861],{},"Se eu tivesse que resumir o raciocínio em uma sequência de perguntas:",[2863,2864,2865,2890,2906,2924,2933],"ol",{},[2866,2867,2868,2874,2875,406,2877,2879,2880,2882,2883,619,2885,2780,2887,2889],"li",{},[294,2869,2870,2871,2873],{},"Esse ",[55,2872,57],{}," foi inicializado antes da primeira escrita?"," Zero value de ",[55,2876,57],{},[55,2878,61],{},", e ",[55,2881,61],{}," aceita leitura, ",[55,2884,500],{},[55,2886,622],{},[55,2888,589],{}," — só não aceita escrita. Se o campo vem de um struct literal parcial ou de um zero value implícito, confira.",[2866,2891,2892,2898,2899,2902,2903,2905],{},[294,2893,2894,2895,2897],{},"Meu código depende da ordem em que o ",[55,2896,622],{}," devolve os elementos?"," Se depende, isso é um bug latente, não uma coincidência que ainda não apareceu. Ordene as chaves explicitamente (",[55,2900,2901],{},"slices.Sorted(maps.Keys(m))",") ou deixe o ",[55,2904,982],{}," fazer isso por você na borda de serialização.",[2866,2907,2908,2913,2914,2719,2916,2722,2918,2920,2921,2923],{},[294,2909,2870,2910,2912],{},[55,2911,57],{}," é acessado por mais de uma goroutine?"," Se só há leitura, nenhuma sincronização é necessária. No instante em que existe uma escrita concorrente, escolha entre ",[55,2915,57],{},[55,2917,2353],{},[55,2919,2356],{}," (o caso geral) ou ",[55,2922,2041],{}," (escrita única com muitas leituras, ou chaves disjuntas por goroutine).",[2866,2925,2926,2932],{},[294,2927,2928,2929,2931],{},"Meus testes de concorrência rodam com ",[55,2930,1951],{},"?"," Se não rodam, \"nunca vi crashar\" não significa \"está correto\".",[2866,2934,2935,2940],{},[294,2936,2870,2937,2939],{},[55,2938,57],{}," já teve um pico de tamanho e hoje está pequeno?"," Se teve, ele ainda está segurando a memória do pico. Recriar é uma opção legítima.",[45,2942,2943,2944,179],{},"O fio que conecta os três assuntos deste artigo é o mesmo que já apareceu nos anteriores: Go tenta te dar previsibilidade de graça (zero value determinístico, verificação de corrida embutida no runtime) e, no ponto exato em que a previsibilidade custaria caro demais para o caso comum, ela para. A ordem de iteração podia ser estável e não é, de propósito, porque estabilidade acidental vira dependência de manutenção. A escrita concorrente podia ser silenciosamente permitida e não é, porque silenciosa é pior que crash. Entender onde essa linha foi traçada é entender o ",[55,2945,57],{},[315,2947,2949],{"id":2948},"referências","Referências",[2951,2952,2953,2963,2972,2979,2986,3005,3024,3030,3045,3057,3075,3091,3105,3118],"ul",{},[2866,2954,2955,2959,2960,2962],{},[49,2956,2958],{"href":339,"rel":2957},[341],"The Go Programming Language Specification — Map types"," — definição de map, zero value ",[55,2961,61],{}," e a exceção de \"nenhum elemento pode ser adicionado\".",[2866,2964,2965,2969,2970,179],{},[49,2966,2968],{"href":1182,"rel":2967},[341],"The Go Programming Language Specification — For statements"," — o texto exato sobre ordem de iteração não especificada e comportamento com inserção\u002Fremoção durante o ",[55,2971,622],{},[2866,2973,2974,2978],{},[49,2975,2977],{"href":1192,"rel":2976},[341],"Go Blog — Go maps in action"," — confirmação, desde 2013, de que a ordem de iteração já era documentada como não garantida.",[2866,2980,2981,2985],{},[49,2982,2984],{"href":1697,"rel":2983},[341],"Go FAQ — Why are map operations not defined to be atomic?"," — a posição oficial sobre segurança de leitura concorrente e a recomendação de sincronização para escrita.",[2866,2987,2988,2993,2994,2722,2997,3000,3001,3004],{},[49,2989,2991],{"href":1289,"rel":2990},[341],[55,2992,1293],{}," — o iterador do Swiss Table map, os campos ",[55,2995,2996],{},"entryOffset",[55,2998,2999],{},"dirOffset"," e a chamada a ",[55,3002,3003],{},"rand()"," na inicialização.",[2866,3006,3007,3013,3014,3016,3017,3020,3021,179],{},[49,3008,3011],{"href":3009,"rel":3010},"https:\u002F\u002Fgithub.com\u002Fgolang\u002Fgo\u002Fblob\u002Fmaster\u002Fsrc\u002Finternal\u002Fruntime\u002Fmaps\u002Fmap.go",[341],[55,3012,2011],{}," — o campo ",[55,3015,2007],{}," e as chamadas a ",[55,3018,3019],{},"fatal(\"concurrent map writes\")"," \u002F ",[55,3022,3023],{},"fatal(\"concurrent map read and map write\")",[2866,3025,3026,3029],{},[49,3027,2001],{"href":1999,"rel":3028},[341]," — a introdução da detecção best-effort de uso concorrente incorreto de maps.",[2866,3031,3032,3037,3038,3040,3041,2780,3043,179],{},[49,3033,3036],{"href":3034,"rel":3035},"https:\u002F\u002Fgo.dev\u002Fdoc\u002Fgo1.21",[341],"Go 1.21 Release Notes"," — o builtin ",[55,3039,1021],{}," e a chegada dos pacotes ",[55,3042,1317],{},[55,3044,1313],{},[2866,3046,3047,3052,3053,2722,3055,179],{},[49,3048,3051],{"href":3049,"rel":3050},"https:\u002F\u002Fgo.dev\u002Fdoc\u002Fgo1.23",[341],"Go 1.23 Release Notes"," — range-over-func e as funções baseadas em iteradores em ",[55,3054,1313],{},[55,3056,1317],{},[2866,3058,3059,3062,3063,3065,3066,3068,3069,3071,3072,3074],{},[49,3060,374],{"href":372,"rel":3061},[341]," — a nova implementação de ",[55,3064,57],{}," baseada em Swiss Tables (",[55,3067,380],{},") e a reimplementação de ",[55,3070,2041],{}," sobre uma hash-trie concorrente (",[55,3073,2834],{},").",[2866,3076,3077,3084,3085,3088,3089,179],{},[49,3078,3081],{"href":3079,"rel":3080},"https:\u002F\u002Fgithub.com\u002Fgolang\u002Fgo\u002Fblob\u002Fmaster\u002Fsrc\u002Finternal\u002Fsync\u002Fhashtriemap.go",[341],[55,3082,3083],{},"internal\u002Fsync\u002Fhashtriemap.go"," — a implementação de ",[55,3086,3087],{},"HashTrieMap"," que hoje serve de base para ",[55,3090,2041],{},[2866,3092,3093,3101,3102,3104],{},[49,3094,1520,3096,3098,3099],{"href":2056,"rel":3095},[341],[55,3097,2060],{}," — ",[55,3100,2050],{}," — os dois casos de uso recomendados para ",[55,3103,2041],{}," e a recomendação padrão de usar map comum com locking.",[2866,3106,3107,3114,3115,3117],{},[49,3108,1520,3110,3098,3112],{"href":1518,"rel":3109},[341],[55,3111,982],{},[55,3113,1525],{}," — a ordenação das chaves de map na serialização e a diferença de comportamento em relação ao ",[55,3116,1667],{}," experimental.",[2866,3119,3120,3124,3125,3127],{},[49,3121,3123],{"href":2023,"rel":3122},[341],"Go Blog — Race Detector"," — a ferramenta correta para detectar corridas de forma confiável, em vez de depender do ",[55,3126,1974],{}," best-effort.",[3129,3130,3131],"style",{},"html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .swQdS, html code.shiki .swQdS{--shiki-light:#E53935;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":72,"searchDepth":97,"depth":97,"links":3133},[3134,3135,3136,3139,3142,3143,3144,3145],{"id":317,"depth":97,"text":318},{"id":399,"depth":97,"text":400},{"id":1028,"depth":97,"text":1029,"children":3137},[3138],{"id":1303,"depth":119,"text":1304},{"id":1679,"depth":97,"text":1680,"children":3140},[3141],{"id":2034,"depth":119,"text":2035},{"id":2421,"depth":97,"text":2422},{"id":2731,"depth":97,"text":2732},{"id":2857,"depth":97,"text":2858},{"id":2948,"depth":97,"text":2949},"2026-08-05","Como o zero value de um map se comporta em Go, por que a ordem de iteração é aleatória de propósito, e como sincronizar acesso concorrente sem escolher a ferramenta errada.","md","https:\u002F\u002Fencrypted-tbn0.gstatic.com\u002Fimages?q=tbn:ANd9GcQvXqKsNKgbxpINxN06GeyY8UPggpH6RUlMZtcJwYptYOPdaD3k4-nHOzd5&s=10",{},{"title":22,"description":3147},"SUXy-AV2TATDXsbF5EP9ZH2VOv7PofjFEfZwEyLMIUk",{"id":3154,"title":10,"author":29,"body":3155,"date":6803,"description":6804,"extension":3148,"image":3149,"meta":6805,"minRead":6806,"navigation":33,"path":11,"seo":6807,"stem":12,"tags":29,"updatedAt":29,"__hash__":6808},"blog\u002Fblog\u002Farrays-e-slices-em-go-capacidade-crescimento-e-append.md",{"type":42,"value":3156,"toc":6782},[3157,3162,3165,3239,3245,3292,3298,3304,3315,3321,3332,3336,3342,3348,3364,3374,3379,3397,3400,3403,3555,3558,3561,3686,3692,3695,3698,3847,3850,3856,3865,3880,3883,3974,3978,3981,4004,4011,4014,4187,4207,4213,4219,4235,4244,4247,4250,4365,4383,4389,4396,4402,4408,4411,4417,4435,4439,4447,4551,4569,4573,4582,4599,4682,4688,4692,4699,4722,4725,4734,4925,4928,4943,4946,4950,4960,4969,4975,4989,4994,5049,5067,5084,5088,5094,5165,5174,5194,5224,5236,5242,5246,5249,5317,5340,5355,5361,5468,5478,5529,5532,5610,5616,5632,5636,5642,5769,5775,5781,5788,5883,5887,5890,5893,5907,5910,6014,6017,6023,6026,6038,6040,6066,6081,6087,6103,6110,6212,6237,6245,6335,6342,6358,6376,6378,6381,6485,6502,6504,6506,6557,6568,6578,6580,6779],[45,3158,47,3159,3161],{},[49,3160,52],{"href":51}," eu comentei de passagem que copiar um slice copia só o seu cabeçalho, não o array por baixo, e que aquilo rendia um artigo inteiro. É este aqui.",[45,3163,3164],{},"Vamos começar pelo fim, com um bug real. Imagine um router HTTP em que cada grupo de rotas herda os middlewares do grupo pai:",[67,3166,3168],{"className":69,"code":3167,"language":71,"meta":72,"style":72},"func (r *Router) Group(m Middleware) *Router {\n    return &Router{middlewares: append(r.middlewares, m)}\n}\n",[55,3169,3170,3203,3235],{"__ignoreMap":72},[76,3171,3172,3174,3176,3178,3180,3183,3185,3188,3190,3192,3195,3197,3199,3201],{"class":78,"line":79},[76,3173,134],{"class":82},[76,3175,137],{"class":93},[76,3177,141],{"class":140},[76,3179,144],{"class":82},[76,3181,3182],{"class":86},"Router",[76,3184,150],{"class":93},[76,3186,3187],{"class":153}," Group",[76,3189,157],{"class":93},[76,3191,471],{"class":140},[76,3193,3194],{"class":86}," Middleware",[76,3196,150],{"class":93},[76,3198,726],{"class":82},[76,3200,3182],{"class":86},[76,3202,94],{"class":93},[76,3204,3205,3207,3209,3211,3213,3216,3218,3220,3222,3225,3227,3229,3231,3233],{"class":78,"line":97},[76,3206,198],{"class":197},[76,3208,737],{"class":82},[76,3210,3182],{"class":86},[76,3212,742],{"class":93},[76,3214,3215],{"class":100},"middlewares",[76,3217,359],{"class":93},[76,3219,1129],{"class":153},[76,3221,157],{"class":93},[76,3223,3224],{"class":100},"r",[76,3226,179],{"class":93},[76,3228,3215],{"class":100},[76,3230,518],{"class":93},[76,3232,527],{"class":100},[76,3234,765],{"class":93},[76,3236,3237],{"class":78,"line":119},[76,3238,122],{"class":93},[45,3240,3241,3242,3244],{},"A função tem uma linha, não tem ponteiro solto, não tem concorrência, e devolve um ",[55,3243,3182],{}," novo em vez de mutar o antigo. Parece o tipo de código que não tem como dar errado. Agora o uso:",[67,3246,3248],{"className":69,"code":3247,"language":71,"meta":72,"style":72},"admin := root.Group(requireAdmin)\npublic := root.Group(rateLimit)\n",[55,3249,3250,3272],{"__ignoreMap":72},[76,3251,3252,3255,3257,3260,3262,3265,3267,3270],{"class":78,"line":79},[76,3253,3254],{"class":100},"admin ",[76,3256,524],{"class":82},[76,3258,3259],{"class":100}," root",[76,3261,179],{"class":93},[76,3263,3264],{"class":153},"Group",[76,3266,157],{"class":93},[76,3268,3269],{"class":100},"requireAdmin",[76,3271,270],{"class":93},[76,3273,3274,3277,3279,3281,3283,3285,3287,3290],{"class":78,"line":97},[76,3275,3276],{"class":100},"public ",[76,3278,524],{"class":82},[76,3280,3259],{"class":100},[76,3282,179],{"class":93},[76,3284,3264],{"class":153},[76,3286,157],{"class":93},[76,3288,3289],{"class":100},"rateLimit",[76,3291,270],{"class":93},[67,3293,3296],{"className":3294,"code":3295,"language":276,"meta":72},[274],"root: len=3 cap=4 [logging recover requestID]\n\nadmin : [logging recover requestID rateLimit]\npublic: [logging recover requestID rateLimit]\n",[55,3297,3295],{"__ignoreMap":72},[45,3299,3300,3301,3303],{},"O router de admin ficou sem o middleware de autenticação. Ninguém sobrescreveu nada de propósito, e o código está sintaticamente correto. O que aconteceu é que os dois ",[55,3302,696],{}," escreveram na mesma posição do mesmo array, e o segundo passou por cima do primeiro.",[45,3305,3306,3307,3310,3311,3314],{},"Repare no ",[55,3308,3309],{},"cap=4"," com ",[55,3312,3313],{},"len=3",". Se fossem iguais, o bug não aconteceria. Ou seja: esse código funciona ou não dependendo de quantos middlewares o pai tinha. Esse é o tipo de bug que passa em teste, passa em code review e aparece em produção seis meses depois, quando alguém adiciona um middleware a mais.",[45,3316,3317,3318,3320],{},"A proposta deste artigo é destrinchar exatamente isso: primeiro a diferença entre array e slice, depois o que é capacidade e por que ela não é um detalhe, como o ",[55,3319,696],{}," faz o slice crescer, e por fim como dois slices acabam enxergando a mesma memória — e o que fazer a respeito.",[288,3322,3323],{},[45,3324,292,3325,3327,3328,3331],{},[294,3326,296],{}," no ",[55,3329,3330],{},"gc"," (o compilador oficial). Números concretos de capacidade são detalhes de implementação e já mudaram entre versões; existe uma seção sobre isso mais adiante.",[315,3333,3335],{"id":3334},"duas-coisas-diferentes-com-nomes-parecidos","Duas coisas diferentes com nomes parecidos",[45,3337,3338,3339,3341],{},"Antes de falar do ",[55,3340,696],{},", vale separar dois tipos que a sintaxe faz parecer irmãos e que não são a mesma coisa.",[45,3343,321,3344,3347],{},[294,3345,3346],{},"array"," é um valor autocontido, e o tamanho faz parte do tipo. A spec é direta:",[326,3349,3350,3355],{},[45,3351,3352,3353,333],{},"\"O tamanho faz parte do tipo do array; ele precisa ser avaliado como uma constante não negativa representável por um valor do tipo ",[55,3354,762],{},[45,3356,336,3357],{},[49,3358,342,3361],{"href":3359,"rel":3360},"https:\u002F\u002Fgo.dev\u002Fref\u002Fspec#Array_types",[341],[344,3362,3363],{},"Array types",[45,3365,3366,3367,2780,3370,3373],{},"Isso tem uma consequência que assusta quem vem de C: ",[55,3368,3369],{},"[3]int",[55,3371,3372],{},"[4]int"," são tipos diferentes. Não dá para passar um onde se espera o outro, e não dá nem para compará-los.",[45,3375,321,3376,3378],{},[294,3377,690],{},", por outro lado, não guarda os dados. Ele descreve um pedaço de um array que mora em outro lugar:",[326,3380,3381,3388],{},[45,3382,3383,3384,3387],{},"\"Um slice é um descritor para um segmento contíguo de um ",[344,3385,3386],{},"array subjacente"," e dá acesso a uma sequência numerada de elementos daquele array. (...) Um slice, uma vez inicializado, está sempre associado a um array subjacente que guarda seus elementos. Um slice, portanto, compartilha armazenamento com seu array e com outros slices do mesmo array; por outro lado, arrays distintos sempre representam armazenamentos distintos.\"",[45,3389,336,3390],{},[49,3391,342,3394],{"href":3392,"rel":3393},"https:\u002F\u002Fgo.dev\u002Fref\u002Fspec#Slice_types",[341],[344,3395,3396],{},"Slice types",[45,3398,3399],{},"Essa última frase é o artigo inteiro em uma linha. Guarde ela.",[45,3401,3402],{},"A diferença aparece na hora de medir:",[67,3404,3406],{"className":69,"code":3405,"language":71,"meta":72,"style":72},"package main\n\nimport (\n    \"fmt\"\n    \"unsafe\"\n)\n\nfunc main() {\n    var arr [1000]int\n    var sl []int\n\n    fmt.Println(unsafe.Sizeof(arr)) \u002F\u002F 8000\n    fmt.Println(unsafe.Sizeof(sl))  \u002F\u002F 24\n}\n",[55,3407,3408,3416,3420,3428,3437,3446,3450,3454,3465,3482,3494,3498,3525,3551],{"__ignoreMap":72},[76,3409,3410,3413],{"class":78,"line":79},[76,3411,3412],{"class":82},"package",[76,3414,3415],{"class":86}," main\n",[76,3417,3418],{"class":78,"line":97},[76,3419,128],{"emptyLinePlaceholder":33},[76,3421,3422,3425],{"class":78,"line":119},[76,3423,3424],{"class":197},"import",[76,3426,3427],{"class":93}," (\n",[76,3429,3430,3433,3435],{"class":78,"line":125},[76,3431,3432],{"class":260},"    \"",[76,3434,461],{"class":86},[76,3436,2530],{"class":260},[76,3438,3439,3441,3444],{"class":78,"line":131},[76,3440,3432],{"class":260},[76,3442,3443],{"class":86},"unsafe",[76,3445,2530],{"class":260},[76,3447,3448],{"class":78,"line":173},[76,3449,270],{"class":93},[76,3451,3452],{"class":78,"line":194},[76,3453,128],{"emptyLinePlaceholder":33},[76,3455,3456,3458,3461,3463],{"class":78,"line":221},[76,3457,134],{"class":82},[76,3459,3460],{"class":153}," main",[76,3462,723],{"class":93},[76,3464,94],{"class":93},[76,3466,3467,3470,3473,3475,3478,3480],{"class":78,"line":581},[76,3468,3469],{"class":82},"    var",[76,3471,3472],{"class":100}," arr ",[76,3474,106],{"class":93},[76,3476,3477],{"class":217},"1000",[76,3479,113],{"class":93},[76,3481,116],{"class":109},[76,3483,3484,3486,3489,3492],{"class":78,"line":586},[76,3485,3469],{"class":82},[76,3487,3488],{"class":100}," sl ",[76,3490,3491],{"class":93},"[]",[76,3493,116],{"class":109},[76,3495,3496],{"class":78,"line":946},[76,3497,128],{"emptyLinePlaceholder":33},[76,3499,3500,3502,3504,3506,3508,3510,3512,3515,3517,3520,3522],{"class":78,"line":1932},[76,3501,1444],{"class":100},[76,3503,179],{"class":93},[76,3505,466],{"class":153},[76,3507,157],{"class":93},[76,3509,3443],{"class":100},[76,3511,179],{"class":93},[76,3513,3514],{"class":153},"Sizeof",[76,3516,157],{"class":93},[76,3518,3519],{"class":100},"arr",[76,3521,507],{"class":93},[76,3523,3524],{"class":486}," \u002F\u002F 8000\n",[76,3526,3527,3529,3531,3533,3535,3537,3539,3541,3543,3546,3548],{"class":78,"line":1937},[76,3528,1444],{"class":100},[76,3530,179],{"class":93},[76,3532,466],{"class":153},[76,3534,157],{"class":93},[76,3536,3443],{"class":100},[76,3538,179],{"class":93},[76,3540,3514],{"class":153},[76,3542,157],{"class":93},[76,3544,3545],{"class":100},"sl",[76,3547,507],{"class":93},[76,3549,3550],{"class":486},"  \u002F\u002F 24\n",[76,3552,3553],{"class":78,"line":2281},[76,3554,122],{"class":93},[45,3556,3557],{},"O array ocupa os 8000 bytes que ele promete. O slice ocupa 24 bytes em qualquer plataforma de 64 bits, independentemente de descrever três elementos ou três milhões, porque ele é só um ponteiro e dois inteiros.",[45,3559,3560],{},"E isso não é curiosidade acadêmica: array é valor, então passar um array para uma função copia o array inteiro.",[67,3562,3564],{"className":69,"code":3563,"language":71,"meta":72,"style":72},"package arrbench\n\ntype Buffer [4096]byte\n\n\u002F\u002Fgo:noinline\nfunc firstArray(b Buffer) byte { return b[0] } \u002F\u002F copia 4 KiB para ler 1 byte\n\n\u002F\u002Fgo:noinline\nfunc firstSlice(b []byte) byte { return b[0] } \u002F\u002F copia 24 bytes de cabeçalho\n",[55,3565,3566,3573,3577,3595,3599,3604,3642,3646,3650],{"__ignoreMap":72},[76,3567,3568,3570],{"class":78,"line":79},[76,3569,3412],{"class":82},[76,3571,3572],{"class":86}," arrbench\n",[76,3574,3575],{"class":78,"line":97},[76,3576,128],{"emptyLinePlaceholder":33},[76,3578,3579,3581,3584,3587,3590,3592],{"class":78,"line":119},[76,3580,83],{"class":82},[76,3582,3583],{"class":86}," Buffer",[76,3585,3586],{"class":93}," [",[76,3588,3589],{"class":217},"4096",[76,3591,113],{"class":93},[76,3593,3594],{"class":109},"byte\n",[76,3596,3597],{"class":78,"line":125},[76,3598,128],{"emptyLinePlaceholder":33},[76,3600,3601],{"class":78,"line":131},[76,3602,3603],{"class":486},"\u002F\u002Fgo:noinline\n",[76,3605,3606,3608,3611,3613,3615,3617,3619,3622,3624,3627,3630,3632,3634,3636,3639],{"class":78,"line":173},[76,3607,134],{"class":82},[76,3609,3610],{"class":153}," firstArray",[76,3612,157],{"class":93},[76,3614,1596],{"class":140},[76,3616,3583],{"class":86},[76,3618,150],{"class":93},[76,3620,3621],{"class":109}," byte",[76,3623,1788],{"class":93},[76,3625,3626],{"class":197}," return",[76,3628,3629],{"class":100}," b",[76,3631,106],{"class":93},[76,3633,1006],{"class":217},[76,3635,113],{"class":93},[76,3637,3638],{"class":93}," }",[76,3640,3641],{"class":486}," \u002F\u002F copia 4 KiB para ler 1 byte\n",[76,3643,3644],{"class":78,"line":194},[76,3645,128],{"emptyLinePlaceholder":33},[76,3647,3648],{"class":78,"line":221},[76,3649,3603],{"class":486},[76,3651,3652,3654,3657,3659,3661,3663,3665,3667,3669,3671,3673,3675,3677,3679,3681,3683],{"class":78,"line":581},[76,3653,134],{"class":82},[76,3655,3656],{"class":153}," firstSlice",[76,3658,157],{"class":93},[76,3660,1596],{"class":140},[76,3662,1068],{"class":93},[76,3664,852],{"class":109},[76,3666,150],{"class":93},[76,3668,3621],{"class":109},[76,3670,1788],{"class":93},[76,3672,3626],{"class":197},[76,3674,3629],{"class":100},[76,3676,106],{"class":93},[76,3678,1006],{"class":217},[76,3680,113],{"class":93},[76,3682,3638],{"class":93},[76,3684,3685],{"class":486}," \u002F\u002F copia 24 bytes de cabeçalho\n",[67,3687,3690],{"className":3688,"code":3689,"language":276,"meta":72},[274],"BenchmarkArrayParam-8    23730294    48.16  ns\u002Fop    0 B\u002Fop    0 allocs\u002Fop\nBenchmarkSliceParam-8   927475783     1.375 ns\u002Fop    0 B\u002Fop    0 allocs\u002Fop\n",[55,3691,3689],{"__ignoreMap":72},[45,3693,3694],{},"Os dois corpos são idênticos. A diferença de 47 nanossegundos é a cópia dos 4 KiB que acontece antes de a função começar. Não há alocação em nenhum dos dois casos — o custo é puramente de cópia, e é por isso que arrays grandes quase nunca aparecem como parâmetro em código Go.",[45,3696,3697],{},"Em compensação, ser um valor autocontido dá ao array duas capacidades que o slice não tem:",[67,3699,3701],{"className":69,"code":3700,"language":71,"meta":72,"style":72},"a := [3]int{1, 2, 3}\nb := [3]int{1, 2, 3}\nfmt.Println(a == b) \u002F\u002F true\n\nroutes := map[[2]string]int{\n    {\"GET\", \"\u002Fusers\"}: 1, \u002F\u002F array como chave de map\n}\n",[55,3702,3703,3734,3763,3783,3787,3812,3843],{"__ignoreMap":72},[76,3704,3705,3708,3710,3712,3715,3717,3719,3721,3724,3726,3728,3730,3732],{"class":78,"line":79},[76,3706,3707],{"class":100},"a ",[76,3709,524],{"class":82},[76,3711,3586],{"class":93},[76,3713,3714],{"class":217},"3",[76,3716,113],{"class":93},[76,3718,762],{"class":109},[76,3720,742],{"class":93},[76,3722,3723],{"class":217},"1",[76,3725,518],{"class":93},[76,3727,1575],{"class":217},[76,3729,518],{"class":93},[76,3731,1589],{"class":217},[76,3733,122],{"class":93},[76,3735,3736,3739,3741,3743,3745,3747,3749,3751,3753,3755,3757,3759,3761],{"class":78,"line":97},[76,3737,3738],{"class":100},"b ",[76,3740,524],{"class":82},[76,3742,3586],{"class":93},[76,3744,3714],{"class":217},[76,3746,113],{"class":93},[76,3748,762],{"class":109},[76,3750,742],{"class":93},[76,3752,3723],{"class":217},[76,3754,518],{"class":93},[76,3756,1575],{"class":217},[76,3758,518],{"class":93},[76,3760,1589],{"class":217},[76,3762,122],{"class":93},[76,3764,3765,3767,3769,3771,3773,3775,3777,3779,3781],{"class":78,"line":119},[76,3766,461],{"class":100},[76,3768,179],{"class":93},[76,3770,466],{"class":153},[76,3772,157],{"class":93},[76,3774,3707],{"class":100},[76,3776,387],{"class":82},[76,3778,3629],{"class":100},[76,3780,150],{"class":93},[76,3782,969],{"class":486},[76,3784,3785],{"class":78,"line":125},[76,3786,128],{"emptyLinePlaceholder":33},[76,3788,3789,3792,3794,3796,3799,3802,3804,3806,3808,3810],{"class":78,"line":131},[76,3790,3791],{"class":100},"routes ",[76,3793,524],{"class":82},[76,3795,1054],{"class":82},[76,3797,3798],{"class":93},"[[",[76,3800,3801],{"class":217},"2",[76,3803,113],{"class":93},[76,3805,110],{"class":109},[76,3807,113],{"class":93},[76,3809,762],{"class":109},[76,3811,573],{"class":93},[76,3813,3814,3817,3819,3822,3824,3826,3828,3831,3833,3836,3838,3840],{"class":78,"line":173},[76,3815,3816],{"class":93},"    {",[76,3818,261],{"class":260},[76,3820,3821],{"class":264},"GET",[76,3823,261],{"class":260},[76,3825,518],{"class":93},[76,3827,598],{"class":260},[76,3829,3830],{"class":264},"\u002Fusers",[76,3832,261],{"class":260},[76,3834,3835],{"class":93},"}:",[76,3837,1561],{"class":217},[76,3839,518],{"class":93},[76,3841,3842],{"class":486}," \u002F\u002F array como chave de map\n",[76,3844,3845],{"class":78,"line":194},[76,3846,122],{"class":93},[45,3848,3849],{},"Arrays são comparáveis se o tipo dos elementos for comparável, e por isso servem como chave de map. Slices não:",[67,3851,3854],{"className":3852,"code":3853,"language":276,"meta":72},[274],"invalid operation: a == b (slice can only be compared to nil)\n",[55,3855,3853],{"__ignoreMap":72},[45,3857,3858,3859,3861,3862,179],{},"O motivo é o mesmo compartilhamento de armazenamento citado na spec: dois slices podem apontar para o mesmo array, ou para arrays diferentes com o mesmo conteúdo, e não existe uma resposta óbvia para o que ",[55,3860,387],{}," deveria significar aí. Para comparar conteúdo existe ",[55,3863,3864],{},"slices.Equal",[985,3866,3867,3874],{},[45,3868,3869,3870,3873],{},"A restrição é contagiosa. Uma struct só é comparável se todos os campos forem comparáveis, então basta um campo ",[55,3871,3872],{},"[]string"," para o tipo inteiro deixar de servir como chave de map:",[67,3875,3878],{"className":3876,"code":3877,"language":276,"meta":72},[274],"invalid operation: x == y (struct containing []string cannot be compared)\n",[55,3879,3877],{"__ignoreMap":72},[45,3881,3882],{},"Resumindo a diferença:",[2734,3884,3885,3903],{},[2737,3886,3887],{},[2740,3888,3889,3891,3897],{},[2743,3890],{},[2743,3892,3893,3894],{},"Array ",[55,3895,3896],{},"[N]T",[2743,3898,3899,3900],{},"Slice ",[55,3901,3902],{},"[]T",[2750,3904,3905,3916,3932,3943,3962],{},[2740,3906,3907,3910,3913],{},[2755,3908,3909],{},"Tamanho",[2755,3911,3912],{},"fixo, faz parte do tipo",[2755,3914,3915],{},"variável em tempo de execução",[2740,3917,3918,3921,3924],{},[2755,3919,3920],{},"O que a variável guarda",[2755,3922,3923],{},"os elementos",[2755,3925,3926,3927,2780,3929],{},"ponteiro, ",[55,3928,500],{},[55,3930,3931],{},"cap",[2740,3933,3934,3937,3940],{},[2755,3935,3936],{},"Custo de copiar",[2755,3938,3939],{},"proporcional ao tamanho",[2755,3941,3942],{},"24 bytes (64 bits)",[2740,3944,3945,3950,3957],{},[2755,3946,3947,3948],{},"Comparável com ",[55,3949,387],{},[2755,3951,3952,3953,3956],{},"sim, se ",[55,3954,3955],{},"T"," for comparável",[2755,3958,3959,3960],{},"só contra ",[55,3961,61],{},[2740,3963,3964,3967,3970],{},[2755,3965,3966],{},"Zero value",[2755,3968,3969],{},"todos os elementos zerados",[2755,3971,3972],{},[55,3973,61],{},[315,3975,3977],{"id":3976},"parte-1-o-cabeçalho-de-três-palavras","Parte 1: o cabeçalho de três palavras",[45,3979,3980],{},"Aqueles 24 bytes são três palavras: um ponteiro para o início do segmento, o comprimento e a capacidade. O blog oficial descreve o slice assim:",[326,3982,3983,3994],{},[45,3984,3985,3986,3989,3990,3993],{},"\"Uma slice é uma estrutura de dados que descreve uma seção contígua de um array guardado separadamente da própria variável slice. ",[344,3987,3988],{},"Uma slice não é um array",". Uma slice ",[344,3991,3992],{},"descreve"," um pedaço de um array.\"",[45,3995,336,3996],{},[49,3997,4000,4001],{"href":3998,"rel":3999},"https:\u002F\u002Fgo.dev\u002Fblog\u002Fslices",[341],"Go Blog, ",[344,4002,4003],{},"Arrays, slices (and strings): The mechanics of 'append'",[45,4005,4006,4007,4010],{},"E aqui está o ponto que reorganiza o entendimento, e que é a continuação direta do artigo anterior: ",[294,4008,4009],{},"o cabeçalho também é um valor",". Quando você passa um slice para uma função, o cabeçalho é copiado. Os dois cabeçalhos apontam para o mesmo array, mas são cabeçalhos independentes.",[45,4012,4013],{},"Isso explica um comportamento que parece contraditório:",[67,4015,4017],{"className":69,"code":4016,"language":71,"meta":72,"style":72},"package main\n\nimport \"fmt\"\n\nfunc mutate(s []int) {\n    s[0] = 99            \u002F\u002F o chamador vê\n    s = append(s, 4)     \u002F\u002F o chamador NÃO vê\n}\n\nfunc main() {\n    s := []int{1, 2, 3}\n    mutate(s)\n    fmt.Println(s, len(s)) \u002F\u002F [99 2 3] 3\n}\n",[55,4018,4019,4025,4029,4039,4043,4063,4082,4105,4109,4113,4123,4147,4158,4183],{"__ignoreMap":72},[76,4020,4021,4023],{"class":78,"line":79},[76,4022,3412],{"class":82},[76,4024,3415],{"class":86},[76,4026,4027],{"class":78,"line":97},[76,4028,128],{"emptyLinePlaceholder":33},[76,4030,4031,4033,4035,4037],{"class":78,"line":119},[76,4032,3424],{"class":197},[76,4034,598],{"class":260},[76,4036,461],{"class":86},[76,4038,2530],{"class":260},[76,4040,4041],{"class":78,"line":125},[76,4042,128],{"emptyLinePlaceholder":33},[76,4044,4045,4047,4050,4052,4055,4057,4059,4061],{"class":78,"line":131},[76,4046,134],{"class":82},[76,4048,4049],{"class":153}," mutate",[76,4051,157],{"class":93},[76,4053,4054],{"class":140},"s",[76,4056,1068],{"class":93},[76,4058,762],{"class":109},[76,4060,150],{"class":93},[76,4062,94],{"class":93},[76,4064,4065,4068,4070,4072,4074,4076,4079],{"class":78,"line":173},[76,4066,4067],{"class":100},"    s",[76,4069,106],{"class":93},[76,4071,1006],{"class":217},[76,4073,113],{"class":93},[76,4075,664],{"class":82},[76,4077,4078],{"class":217}," 99",[76,4080,4081],{"class":486},"            \u002F\u002F o chamador vê\n",[76,4083,4084,4087,4089,4091,4093,4095,4097,4100,4102],{"class":78,"line":194},[76,4085,4086],{"class":100},"    s ",[76,4088,1126],{"class":82},[76,4090,1129],{"class":153},[76,4092,157],{"class":93},[76,4094,4054],{"class":100},[76,4096,518],{"class":93},[76,4098,4099],{"class":217}," 4",[76,4101,150],{"class":93},[76,4103,4104],{"class":486},"     \u002F\u002F o chamador NÃO vê\n",[76,4106,4107],{"class":78,"line":221},[76,4108,122],{"class":93},[76,4110,4111],{"class":78,"line":581},[76,4112,128],{"emptyLinePlaceholder":33},[76,4114,4115,4117,4119,4121],{"class":78,"line":586},[76,4116,134],{"class":82},[76,4118,3460],{"class":153},[76,4120,723],{"class":93},[76,4122,94],{"class":93},[76,4124,4125,4127,4129,4131,4133,4135,4137,4139,4141,4143,4145],{"class":78,"line":946},[76,4126,4086],{"class":100},[76,4128,524],{"class":82},[76,4130,1068],{"class":93},[76,4132,762],{"class":109},[76,4134,742],{"class":93},[76,4136,3723],{"class":217},[76,4138,518],{"class":93},[76,4140,1575],{"class":217},[76,4142,518],{"class":93},[76,4144,1589],{"class":217},[76,4146,122],{"class":93},[76,4148,4149,4152,4154,4156],{"class":78,"line":1932},[76,4150,4151],{"class":153},"    mutate",[76,4153,157],{"class":93},[76,4155,4054],{"class":100},[76,4157,270],{"class":93},[76,4159,4160,4162,4164,4166,4168,4170,4172,4174,4176,4178,4180],{"class":78,"line":1937},[76,4161,1444],{"class":100},[76,4163,179],{"class":93},[76,4165,466],{"class":153},[76,4167,157],{"class":93},[76,4169,4054],{"class":100},[76,4171,518],{"class":93},[76,4173,1095],{"class":153},[76,4175,157],{"class":93},[76,4177,4054],{"class":100},[76,4179,507],{"class":93},[76,4181,4182],{"class":486}," \u002F\u002F [99 2 3] 3\n",[76,4184,4185],{"class":78,"line":2281},[76,4186,122],{"class":93},[45,4188,4189,4190,4193,4194,4196,4197,4199,4200,4203,4204,4206],{},"As duas linhas parecem mexer no mesmo slice, e só uma tem efeito lá fora. ",[55,4191,4192],{},"s[0] = 99"," escreve no array, que é compartilhado. Já ",[55,4195,696],{}," devolve um cabeçalho novo, com ",[55,4198,500],{}," igual a 4, e atribui esse cabeçalho à cópia local. O ",[55,4201,4202],{},"main"," continua com o cabeçalho antigo, de ",[55,4205,500],{}," 3.",[45,4208,4209,4210,4212],{},"É exatamente por isso que ",[55,4211,696],{}," devolve um valor em vez de mutar o slice no lugar, e por que ignorar esse retorno não é um estilo alternativo, é um erro de compilação:",[67,4214,4217],{"className":4215,"code":4216,"language":276,"meta":72},[274],"append(s, 2) (value of type []int) is not used\n",[55,4218,4216],{"__ignoreMap":72},[288,4220,4221],{},[45,4222,4223,4224,4227,4228,4230,4231,4234],{},"Vale reforçar a assimetria, porque ela é a origem de metade da confusão com slices: ",[294,4225,4226],{},"os elementos são compartilhados, o cabeçalho não é."," Passar um slice adiante permite que a função altere seus dados, mas não permite que ela altere o seu ",[55,4229,500],{},". Se você precisa disso, devolva o slice (o que é idiomático) ou receba um ",[55,4232,4233],{},"*[]T"," (o que raramente é).",[315,4236,4238,4239,4241,4242],{"id":4237},"parte-2-len-não-é-cap","Parte 2: ",[55,4240,500],{}," não é ",[55,4243,3931],{},[45,4245,4246],{},"O comprimento é quantos elementos o slice tem. A capacidade é quantos ele poderia ter sem trocar de array — é a distância entre o começo do slice e o fim do array subjacente.",[45,4248,4249],{},"A distinção parece burocrática até você escrever, quase sempre sem perceber, esta função:",[67,4251,4253],{"className":69,"code":4252,"language":71,"meta":72,"style":72},"\u002F\u002F Errado: make com len, e não com cap.\nfunc orderIDs(orders []Order) []int64 {\n    out := make([]int64, len(orders))\n    for _, o := range orders {\n        out = append(out, o.ID)\n    }\n    return out\n}\n",[55,4254,4255,4260,4285,4307,4327,4351,4355,4361],{"__ignoreMap":72},[76,4256,4257],{"class":78,"line":79},[76,4258,4259],{"class":486},"\u002F\u002F Errado: make com len, e não com cap.\n",[76,4261,4262,4264,4267,4269,4272,4274,4276,4278,4280,4283],{"class":78,"line":97},[76,4263,134],{"class":82},[76,4265,4266],{"class":153}," orderIDs",[76,4268,157],{"class":93},[76,4270,4271],{"class":140},"orders",[76,4273,1068],{"class":93},[76,4275,2475],{"class":86},[76,4277,150],{"class":93},[76,4279,1068],{"class":93},[76,4281,4282],{"class":109},"int64",[76,4284,94],{"class":93},[76,4286,4287,4289,4291,4293,4295,4297,4299,4301,4303,4305],{"class":78,"line":119},[76,4288,1077],{"class":100},[76,4290,524],{"class":82},[76,4292,749],{"class":153},[76,4294,849],{"class":93},[76,4296,4282],{"class":109},[76,4298,518],{"class":93},[76,4300,1095],{"class":153},[76,4302,157],{"class":93},[76,4304,4271],{"class":100},[76,4306,1102],{"class":93},[76,4308,4309,4311,4313,4315,4318,4320,4322,4325],{"class":78,"line":125},[76,4310,1107],{"class":197},[76,4312,1426],{"class":100},[76,4314,518],{"class":93},[76,4316,4317],{"class":100}," o ",[76,4319,524],{"class":82},[76,4321,568],{"class":197},[76,4323,4324],{"class":100}," orders ",[76,4326,573],{"class":93},[76,4328,4329,4331,4333,4335,4337,4339,4341,4344,4346,4349],{"class":78,"line":131},[76,4330,1123],{"class":100},[76,4332,1126],{"class":82},[76,4334,1129],{"class":153},[76,4336,157],{"class":93},[76,4338,1134],{"class":100},[76,4340,518],{"class":93},[76,4342,4343],{"class":100}," o",[76,4345,179],{"class":93},[76,4347,4348],{"class":100},"ID",[76,4350,270],{"class":93},[76,4352,4353],{"class":78,"line":173},[76,4354,1146],{"class":93},[76,4356,4357,4359],{"class":78,"line":194},[76,4358,198],{"class":197},[76,4360,1153],{"class":100},[76,4362,4363],{"class":78,"line":221},[76,4364,122],{"class":93},[45,4366,4367,4368,4371,4372,4375,4376,4379,4380,4382],{},"O ",[55,4369,4370],{},"make([]int64, len(orders))"," cria um slice com ",[55,4373,4374],{},"len(orders)"," elementos ",[294,4377,4378],{},"já preenchidos com o zero value",". Cada ",[55,4381,696],{}," acrescenta depois deles:",[67,4384,4387],{"className":4385,"code":4386,"language":276,"meta":72},[274],"[0 0 0 10 20 30] len=6 cap=6\n",[55,4388,4386],{"__ignoreMap":72},[45,4390,4391,4392,4395],{},"A intenção era reservar espaço; o que aconteceu foi criar os elementos. A forma correta é ",[55,4393,4394],{},"make([]int64, 0, len(orders))",": comprimento zero, capacidade reservada.",[45,4397,4398,4399,4401],{},"O detalhe é que esse bug não é só de correção, ele é mais lento do que não ter otimizado nada. Coletando 1000 ",[55,4400,2475],{}," (32 bytes cada):",[67,4403,4406],{"className":4404,"code":4405,"language":276,"meta":72},[274],"BenchmarkNoPrealloc-8       114913     8935 ns\u002Fop     70208 B\u002Fop    10 allocs\u002Fop\nBenchmarkPrealloc-8         290004     3953 ns\u002Fop     32768 B\u002Fop     1 allocs\u002Fop\nBenchmarkWrongPrealloc-8     77742    15990 ns\u002Fop    155648 B\u002Fop     3 allocs\u002Fop\n",[55,4407,4405],{"__ignoreMap":72},[45,4409,4410],{},"A versão sem pré-alocação faz 10 alocações, porque o slice precisou crescer 10 vezes ao longo do caminho. A versão correta faz uma só, e é 2,3× mais rápida.",[45,4412,4413,4414,4416],{},"A versão com o ",[55,4415,775],{}," errado é a mais interessante das três. Ela termina com um slice de 2000 elementos — os 1000 zeros na frente e os 1000 valores reais depois — e, para chegar lá, alocou três vezes: os 1000 elementos iniciais, e mais duas realocações de crescimento (capacidade 1000 → 1536 → 2304). São os 155.648 B\u002Fop que o benchmark reporta, para produzir um resultado que, além de tudo, está errado. A tentativa de otimizar saiu quase 2× mais cara do que não ter feito nada.",[985,4418,4419],{},[45,4420,4421,4424,4425,4428,4429,4432,4433,179],{},[55,4422,4423],{},"go vet"," não pega esse caso. O linter ",[55,4426,4427],{},"makezero",", disponível no ",[55,4430,4431],{},"golangci-lint",", existe justamente para isso: ele acusa slices criados com comprimento diferente de zero que depois recebem ",[55,4434,696],{},[1301,4436,4438],{"id":4437},"a-capacidade-também-é-um-pedaço-de-array-que-ainda-está-lá","A capacidade também é um pedaço de array que ainda está lá",[45,4440,4441,4442,2780,4444,4446],{},"Uma consequência menos óbvia: os elementos entre ",[55,4443,500],{},[55,4445,3931],{}," não desapareceram, eles só não são acessíveis pelo slice atual. Refatiar até a capacidade os traz de volta:",[67,4448,4450],{"className":69,"code":4449,"language":71,"meta":72,"style":72},"s := []int{1, 2, 3, 4, 5}\nt := s[:2]\n\nfmt.Println(t)          \u002F\u002F [1 2]\nfmt.Println(t[:cap(t)]) \u002F\u002F [1 2 3 4 5]\n",[55,4451,4452,4486,4503,4507,4525],{"__ignoreMap":72},[76,4453,4454,4457,4459,4461,4463,4465,4467,4469,4471,4473,4475,4477,4479,4481,4484],{"class":78,"line":79},[76,4455,4456],{"class":100},"s ",[76,4458,524],{"class":82},[76,4460,1068],{"class":93},[76,4462,762],{"class":109},[76,4464,742],{"class":93},[76,4466,3723],{"class":217},[76,4468,518],{"class":93},[76,4470,1575],{"class":217},[76,4472,518],{"class":93},[76,4474,1589],{"class":217},[76,4476,518],{"class":93},[76,4478,4099],{"class":217},[76,4480,518],{"class":93},[76,4482,4483],{"class":217}," 5",[76,4485,122],{"class":93},[76,4487,4488,4491,4493,4496,4499,4501],{"class":78,"line":97},[76,4489,4490],{"class":100},"t ",[76,4492,524],{"class":82},[76,4494,4495],{"class":100}," s",[76,4497,4498],{"class":93},"[:",[76,4500,3801],{"class":217},[76,4502,538],{"class":93},[76,4504,4505],{"class":78,"line":119},[76,4506,128],{"emptyLinePlaceholder":33},[76,4508,4509,4511,4513,4515,4517,4520,4522],{"class":78,"line":125},[76,4510,461],{"class":100},[76,4512,179],{"class":93},[76,4514,466],{"class":153},[76,4516,157],{"class":93},[76,4518,4519],{"class":100},"t",[76,4521,150],{"class":93},[76,4523,4524],{"class":486},"          \u002F\u002F [1 2]\n",[76,4526,4527,4529,4531,4533,4535,4537,4539,4541,4543,4545,4548],{"class":78,"line":131},[76,4528,461],{"class":100},[76,4530,179],{"class":93},[76,4532,466],{"class":153},[76,4534,157],{"class":93},[76,4536,4519],{"class":100},[76,4538,4498],{"class":93},[76,4540,3931],{"class":153},[76,4542,157],{"class":93},[76,4544,4519],{"class":100},[76,4546,4547],{"class":93},")])",[76,4549,4550],{"class":486}," \u002F\u002F [1 2 3 4 5]\n",[45,4552,4553,4555,4556,4558,4559,4561,4562,4564,4565,4568],{},[55,4554,4519],{}," tem ",[55,4557,500],{}," 2 e ",[55,4560,3931],{}," 5. Encurtar um slice não apaga nada; apenas move o ",[55,4563,500],{},". É por isso que \"cortar\" dados sensíveis com ",[55,4566,4567],{},"s = s[:n]"," não é uma forma de descartá-los.",[1301,4570,4572],{"id":4571},"a-expressão-de-três-índices","A expressão de três índices",[45,4574,4575,4576,619,4579,359],{},"Para limitar a capacidade existe a ",[294,4577,4578],{},"full slice expression",[55,4580,4581],{},"a[low:high:max]",[326,4583,4584,4590],{},[45,4585,4586,4587,333],{},"\"Adicionalmente, ela controla a capacidade do slice resultante, definindo-a como ",[55,4588,4589],{},"max - low",[45,4591,336,4592],{},[49,4593,342,4596],{"href":4594,"rel":4595},"https:\u002F\u002Fgo.dev\u002Fref\u002Fspec#Full_slice_expressions",[341],[344,4597,4598],{},"Full slice expressions",[67,4600,4602],{"className":69,"code":4601,"language":71,"meta":72,"style":72},"all := []int{1, 2, 3, 4, 5}\n\nhead := all[:2]     \u002F\u002F len=2, cap=5\nsafe := all[:2:2]   \u002F\u002F len=2, cap=2\n",[55,4603,4604,4637,4641,4660],{"__ignoreMap":72},[76,4605,4606,4609,4611,4613,4615,4617,4619,4621,4623,4625,4627,4629,4631,4633,4635],{"class":78,"line":79},[76,4607,4608],{"class":100},"all ",[76,4610,524],{"class":82},[76,4612,1068],{"class":93},[76,4614,762],{"class":109},[76,4616,742],{"class":93},[76,4618,3723],{"class":217},[76,4620,518],{"class":93},[76,4622,1575],{"class":217},[76,4624,518],{"class":93},[76,4626,1589],{"class":217},[76,4628,518],{"class":93},[76,4630,4099],{"class":217},[76,4632,518],{"class":93},[76,4634,4483],{"class":217},[76,4636,122],{"class":93},[76,4638,4639],{"class":78,"line":97},[76,4640,128],{"emptyLinePlaceholder":33},[76,4642,4643,4646,4648,4651,4653,4655,4657],{"class":78,"line":119},[76,4644,4645],{"class":100},"head ",[76,4647,524],{"class":82},[76,4649,4650],{"class":100}," all",[76,4652,4498],{"class":93},[76,4654,3801],{"class":217},[76,4656,113],{"class":93},[76,4658,4659],{"class":486},"     \u002F\u002F len=2, cap=5\n",[76,4661,4662,4665,4667,4669,4671,4673,4675,4677,4679],{"class":78,"line":125},[76,4663,4664],{"class":100},"safe ",[76,4666,524],{"class":82},[76,4668,4650],{"class":100},[76,4670,4498],{"class":93},[76,4672,3801],{"class":217},[76,4674,359],{"class":93},[76,4676,3801],{"class":217},[76,4678,113],{"class":93},[76,4680,4681],{"class":486},"   \u002F\u002F len=2, cap=2\n",[45,4683,4684,4685,4687],{},"A diferença entre esses dois é a diferença entre o bug da introdução e o código correto, e é para lá que vamos na Parte 4. Antes, precisamos entender o que o ",[55,4686,696],{}," faz quando a capacidade acaba.",[315,4689,4691],{"id":4690},"parte-3-como-o-slice-cresce","Parte 3: como o slice cresce",[45,4693,4694,4695,4698],{},"A spec descreve o comportamento em uma frase, e note o que ela ",[294,4696,4697],{},"não"," promete:",[326,4700,4701,4713],{},[45,4702,4703,4704,4706,4707,4709,4710,4712],{},"\"Se a capacidade de ",[55,4705,4054],{}," não for grande o suficiente para caber os valores adicionais, ",[55,4708,696],{}," aloca um novo array subjacente, suficientemente grande, que caiba tanto os elementos existentes quanto os adicionais. Caso contrário, ",[55,4711,696],{}," reutiliza o array subjacente.\"",[45,4714,336,4715],{},[49,4716,342,4719],{"href":4717,"rel":4718},"https:\u002F\u002Fgo.dev\u002Fref\u002Fspec#Appending_and_copying_slices",[341],[344,4720,4721],{},"Appending to and copying slices",[45,4723,4724],{},"\"Suficientemente grande\" é tudo o que a linguagem garante. Quanto exatamente é decisão do runtime, e ela mudou ao longo das versões.",[45,4726,4727,4728,2008,4731,359],{},"No Go 1.26, quem decide é ",[55,4729,4730],{},"nextslicecap",[55,4732,4733],{},"runtime\u002Fslice.go",[67,4735,4737],{"className":69,"code":4736,"language":71,"meta":72,"style":72},"func nextslicecap(newLen, oldCap int) int {\n    newcap := oldCap\n    doublecap := newcap + newcap\n    if newLen > doublecap {\n        return newLen\n    }\n\n    const threshold = 256\n    if oldCap \u003C threshold {\n        return doublecap\n    }\n    for {\n        \u002F\u002F Transition from growing 2x for small slices\n        \u002F\u002F to growing 1.25x for large slices. This formula\n        \u002F\u002F gives a smooth-ish transition between the two.\n        newcap += (newcap + 3*threshold) >> 2\n        \u002F\u002F ...\n    }\n}\n",[55,4738,4739,4764,4774,4789,4805,4813,4817,4821,4834,4848,4855,4859,4865,4870,4875,4880,4910,4915,4920],{"__ignoreMap":72},[76,4740,4741,4743,4746,4748,4751,4753,4756,4758,4760,4762],{"class":78,"line":79},[76,4742,134],{"class":82},[76,4744,4745],{"class":153}," nextslicecap",[76,4747,157],{"class":93},[76,4749,4750],{"class":140},"newLen",[76,4752,518],{"class":93},[76,4754,4755],{"class":140}," oldCap",[76,4757,2274],{"class":109},[76,4759,150],{"class":93},[76,4761,2274],{"class":109},[76,4763,94],{"class":93},[76,4765,4766,4769,4771],{"class":78,"line":97},[76,4767,4768],{"class":100},"    newcap ",[76,4770,524],{"class":82},[76,4772,4773],{"class":100}," oldCap\n",[76,4775,4776,4779,4781,4784,4786],{"class":78,"line":119},[76,4777,4778],{"class":100},"    doublecap ",[76,4780,524],{"class":82},[76,4782,4783],{"class":100}," newcap ",[76,4785,2412],{"class":82},[76,4787,4788],{"class":100}," newcap\n",[76,4790,4791,4794,4797,4800,4803],{"class":78,"line":125},[76,4792,4793],{"class":197},"    if",[76,4795,4796],{"class":100}," newLen ",[76,4798,4799],{"class":82},">",[76,4801,4802],{"class":100}," doublecap ",[76,4804,573],{"class":93},[76,4806,4807,4810],{"class":78,"line":131},[76,4808,4809],{"class":197},"        return",[76,4811,4812],{"class":100}," newLen\n",[76,4814,4815],{"class":78,"line":173},[76,4816,1146],{"class":93},[76,4818,4819],{"class":78,"line":194},[76,4820,128],{"emptyLinePlaceholder":33},[76,4822,4823,4826,4829,4831],{"class":78,"line":221},[76,4824,4825],{"class":82},"    const",[76,4827,4828],{"class":1466}," threshold",[76,4830,664],{"class":82},[76,4832,4833],{"class":217}," 256\n",[76,4835,4836,4838,4841,4843,4846],{"class":78,"line":581},[76,4837,4793],{"class":197},[76,4839,4840],{"class":100}," oldCap ",[76,4842,1810],{"class":82},[76,4844,4845],{"class":100}," threshold ",[76,4847,573],{"class":93},[76,4849,4850,4852],{"class":78,"line":586},[76,4851,4809],{"class":197},[76,4853,4854],{"class":100}," doublecap\n",[76,4856,4857],{"class":78,"line":946},[76,4858,1146],{"class":93},[76,4860,4861,4863],{"class":78,"line":1932},[76,4862,1107],{"class":197},[76,4864,94],{"class":93},[76,4866,4867],{"class":78,"line":1937},[76,4868,4869],{"class":486},"        \u002F\u002F Transition from growing 2x for small slices\n",[76,4871,4872],{"class":78,"line":2281},[76,4873,4874],{"class":486},"        \u002F\u002F to growing 1.25x for large slices. This formula\n",[76,4876,4877],{"class":78,"line":2297},[76,4878,4879],{"class":486},"        \u002F\u002F gives a smooth-ish transition between the two.\n",[76,4881,4882,4885,4888,4890,4893,4895,4897,4899,4902,4904,4907],{"class":78,"line":2315},[76,4883,4884],{"class":100},"        newcap ",[76,4886,4887],{"class":82},"+=",[76,4889,137],{"class":93},[76,4891,4892],{"class":100},"newcap ",[76,4894,2412],{"class":82},[76,4896,1589],{"class":217},[76,4898,144],{"class":82},[76,4900,4901],{"class":100},"threshold",[76,4903,150],{"class":93},[76,4905,4906],{"class":82}," >>",[76,4908,4909],{"class":217}," 2\n",[76,4911,4912],{"class":78,"line":2335},[76,4913,4914],{"class":486},"        \u002F\u002F ...\n",[76,4916,4918],{"class":78,"line":4917},18,[76,4919,1146],{"class":93},[76,4921,4923],{"class":78,"line":4922},19,[76,4924,122],{"class":93},[45,4926,4927],{},"São três regras, em ordem:",[2863,4929,4930,4933,4936],{},[2866,4931,4932],{},"Se o que você pediu já passa do dobro da capacidade atual, o runtime usa exatamente o que foi pedido. Adicionar 100 elementos a um slice de capacidade 4 não vai gerar capacidade 8.",[2866,4934,4935],{},"Abaixo de 256 elementos de capacidade, dobra.",[2866,4937,4938,4939,4942],{},"A partir de 256, aplica ",[55,4940,4941],{},"newcap += (newcap + 768) \u002F 4",", que começa em fator 2 e vai caindo suavemente em direção a 1,25.",[45,4944,4945],{},"O objetivo da terceira regra é evitar o degrau que existia antes: até o Go 1.17, o fator caía de 2 para 1,25 de uma vez em 1024 elementos.",[1301,4947,4949],{"id":4948},"o-que-você-realmente-observa","O que você realmente observa",[45,4951,4952,4953,4955,4956,4959],{},"Só que ",[55,4954,4730],{}," não é a última palavra. O valor que ela devolve vira uma quantidade de bytes, e o alocador do Go arredonda essa quantidade para a ",[294,4957,4958],{},"size class"," mais próxima, porque ele não aloca blocos de tamanho arbitrário. A capacidade final é o quanto cabe no bloco arredondado.",[45,4961,4962,4963,4966,4967,359],{},"Medindo o crescimento de um ",[55,4964,4965],{},"[]int"," a partir de ",[55,4968,61],{},[67,4970,4973],{"className":4971,"code":4972,"language":276,"meta":72},[274],"len=1     cap=1\nlen=2     cap=2\nlen=3     cap=4\nlen=5     cap=8\n...\nlen=257   cap=512\nlen=513   cap=848      \u003C- a fórmula diz 832\nlen=849   cap=1280\nlen=1281  cap=1792\n",[55,4974,4972],{"__ignoreMap":72},[45,4976,4977,4978,4981,4982,4985,4986,4988],{},"Em ",[55,4979,4980],{},"cap=512",", a fórmula dá ",[55,4983,4984],{},"512 + (512 + 768)\u002F4 = 832",". Mas 832 elementos de 8 bytes são 6656 bytes, e a size class imediatamente acima é 6784 — que comporta 848 elementos. O runtime não joga fora os 16 elementos que sobraram no bloco, então ",[55,4987,3931],{}," vira 848.",[45,4990,4991,4992,359],{},"Por isso a sequência depende do tipo do elemento, não só da quantidade. Crescendo um elemento por vez a partir de ",[55,4993,61],{},[2734,4995,4996,5009],{},[2737,4997,4998],{},[2740,4999,5000,5003,5006],{},[2743,5001,5002],{},"Tipo",[2743,5004,5005],{},"Tamanho do elemento",[2743,5007,5008],{},"Capacidades observadas",[2750,5010,5011,5024,5036],{},[2740,5012,5013,5018,5021],{},[2755,5014,5015],{},[55,5016,5017],{},"[]byte",[2755,5019,5020],{},"1 B",[2755,5022,5023],{},"8, 16, 32, 64, 128, 256, 512, 896, 1408, 2048",[2740,5025,5026,5030,5033],{},[2755,5027,5028],{},[55,5029,4965],{},[2755,5031,5032],{},"8 B",[2755,5034,5035],{},"1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 848, 1280, 1792",[2740,5037,5038,5043,5046],{},[2755,5039,5040],{},[55,5041,5042],{},"[]Order",[2755,5044,5045],{},"32 B",[2755,5047,5048],{},"1, 2, 4, 8, 16, 35, 71, 151, 303, 575, 1023, 1536",[45,5050,5051,5052,5054,5055,5057,5058,5060,5061,5063,5064,5066],{},"Três coisas para reparar nessa tabela. ",[55,5053,5017],{}," começa em 8, e não em 1, porque a menor size class é de 8 bytes — pedir 1 byte e receber 8 é o piso do alocador. ",[55,5056,4965],{}," segue as potências de dois até 512, onde a fórmula assume e o arredondamento começa a aparecer. E ",[55,5059,5042],{}," sai de 16 direto para 35, um número que não é potência de dois nem múltiplo de nada óbvio: são 32 elementos pela fórmula, arredondados para cima pelo tamanho do bloco, e ainda descontados de um cabeçalho de 8 bytes que o alocador acrescenta porque ",[55,5062,2475],{}," contém uma ",[55,5065,110],{}," — elementos com ponteiros precisam desse metadado para o GC saber o que varrer.",[985,5068,5069],{},[45,5070,5071,5072,5080,5081,5083],{},"A moral prática dessa seção não é decorar a tabela, é o contrário: ",[294,5073,5074,5076,5077,5079],{},[55,5075,3931],{}," depois de um ",[55,5078,696],{}," é um detalhe de implementação."," Se o seu código depende de um valor específico de capacidade, ele vai quebrar em alguma versão futura do Go. O que você pode confiar é no comportamento: o crescimento é geométrico, então uma sequência de N ",[55,5082,696],{}," custa O(N) no total, e não O(N²).",[1301,5085,5087],{"id":5086},"uma-surpresa-que-vem-do-artigo-anterior","Uma surpresa que vem do artigo anterior",[45,5089,5090,5091,5093],{},"Se você rodar o teste acima em uma função onde o slice ",[294,5092,4697],{}," escapa, os números mudam:",[67,5095,5097],{"className":69,"code":5096,"language":71,"meta":72,"style":72},"\u002F\u002Fgo:noinline\nfunc firstCapLocal() int {\n    var s []int\n    s = append(s, 1)\n    return cap(s) \u002F\u002F 4\n}\n",[55,5098,5099,5103,5116,5127,5145,5161],{"__ignoreMap":72},[76,5100,5101],{"class":78,"line":79},[76,5102,3603],{"class":486},[76,5104,5105,5107,5110,5112,5114],{"class":78,"line":97},[76,5106,134],{"class":82},[76,5108,5109],{"class":153}," firstCapLocal",[76,5111,723],{"class":93},[76,5113,2274],{"class":109},[76,5115,94],{"class":93},[76,5117,5118,5120,5123,5125],{"class":78,"line":119},[76,5119,3469],{"class":82},[76,5121,5122],{"class":100}," s ",[76,5124,3491],{"class":93},[76,5126,116],{"class":109},[76,5128,5129,5131,5133,5135,5137,5139,5141,5143],{"class":78,"line":125},[76,5130,4086],{"class":100},[76,5132,1126],{"class":82},[76,5134,1129],{"class":153},[76,5136,157],{"class":93},[76,5138,4054],{"class":100},[76,5140,518],{"class":93},[76,5142,1561],{"class":217},[76,5144,270],{"class":93},[76,5146,5147,5149,5152,5154,5156,5158],{"class":78,"line":131},[76,5148,198],{"class":197},[76,5150,5151],{"class":153}," cap",[76,5153,157],{"class":93},[76,5155,4054],{"class":100},[76,5157,150],{"class":93},[76,5159,5160],{"class":486}," \u002F\u002F 4\n",[76,5162,5163],{"class":78,"line":173},[76,5164,122],{"class":93},[45,5166,5167,5168,5170,5171,5173],{},"Quatro, e não um. E com ",[55,5169,5017],{}," o primeiro ",[55,5172,696],{}," devolve capacidade 32, não 8.",[45,5175,5176,5177,5180,5181,5185,5186,5189,5190,5193],{},"Isso não é o ",[55,5178,5179],{},"growslice"," — é o ",[49,5182,5184],{"href":5183},"\u002Fescape-analysis-em-go-stack-ou-heap","escape analysis"," em ação. Desde o Go 1.25, quando o compilador prova que o slice não sobrevive à função, ele reserva um buffer fixo na stack e usa esse buffer como array inicial. O tamanho padrão é de 32 bytes, definido por ",[55,5187,5188],{},"VariableMakeThreshold"," em ",[55,5191,5192],{},"cmd\u002Fcompile\u002Finternal\u002Fbase\u002Fflag.go",", e a capacidade resultante é o número de elementos que cabem nele:",[67,5195,5197],{"className":69,"code":5196,"language":71,"meta":72,"style":72},"K := maxStackSize \u002F et.Size() \u002F\u002F cmd\u002Fcompile\u002Finternal\u002Fssagen\u002Fssa.go\n",[55,5198,5199],{"__ignoreMap":72},[76,5200,5201,5204,5206,5209,5211,5214,5216,5219,5221],{"class":78,"line":79},[76,5202,5203],{"class":100},"K ",[76,5205,524],{"class":82},[76,5207,5208],{"class":100}," maxStackSize ",[76,5210,2722],{"class":82},[76,5212,5213],{"class":100}," et",[76,5215,179],{"class":93},[76,5217,5218],{"class":153},"Size",[76,5220,723],{"class":93},[76,5222,5223],{"class":486}," \u002F\u002F cmd\u002Fcompile\u002Finternal\u002Fssagen\u002Fssa.go\n",[45,5225,5226,5227,5229,5230,5232,5233,5235],{},"32 \u002F 8 = 4 para ",[55,5228,4965],{},", 32 \u002F 1 = 32 para ",[55,5231,5017],{},", 32 \u002F 32 = 1 para ",[55,5234,5042],{},". Confere com o observado. Basta fazer o slice escapar — atribuindo a uma variável global, por exemplo — para as capacidades voltarem a 1, 8 e 1.",[45,5237,5238,5239,5241],{},"É o mesmo padrão que apareceu no artigo sobre escape analysis: a mesma linha de código produz resultados diferentes conforme o contexto da chamada. Aqui isso é ainda mais uma razão para não tratar ",[55,5240,3931],{}," como um número previsível.",[315,5243,5245],{"id":5244},"parte-4-aliasing-ou-quando-dois-slices-enxergam-o-mesmo-array","Parte 4: aliasing, ou quando dois slices enxergam o mesmo array",[45,5247,5248],{},"Agora dá para voltar ao bug da introdução com as ferramentas certas.",[67,5250,5251],{"className":69,"code":3167,"language":71,"meta":72,"style":72},[55,5252,5253,5283,5313],{"__ignoreMap":72},[76,5254,5255,5257,5259,5261,5263,5265,5267,5269,5271,5273,5275,5277,5279,5281],{"class":78,"line":79},[76,5256,134],{"class":82},[76,5258,137],{"class":93},[76,5260,141],{"class":140},[76,5262,144],{"class":82},[76,5264,3182],{"class":86},[76,5266,150],{"class":93},[76,5268,3187],{"class":153},[76,5270,157],{"class":93},[76,5272,471],{"class":140},[76,5274,3194],{"class":86},[76,5276,150],{"class":93},[76,5278,726],{"class":82},[76,5280,3182],{"class":86},[76,5282,94],{"class":93},[76,5284,5285,5287,5289,5291,5293,5295,5297,5299,5301,5303,5305,5307,5309,5311],{"class":78,"line":97},[76,5286,198],{"class":197},[76,5288,737],{"class":82},[76,5290,3182],{"class":86},[76,5292,742],{"class":93},[76,5294,3215],{"class":100},[76,5296,359],{"class":93},[76,5298,1129],{"class":153},[76,5300,157],{"class":93},[76,5302,3224],{"class":100},[76,5304,179],{"class":93},[76,5306,3215],{"class":100},[76,5308,518],{"class":93},[76,5310,527],{"class":100},[76,5312,765],{"class":93},[76,5314,5315],{"class":78,"line":119},[76,5316,122],{"class":93},[45,5318,5319,4555,5322,2780,5324,5326,5327,5329,5330,5332,5333,5335,5336,5339],{},[55,5320,5321],{},"r.middlewares",[55,5323,3313],{},[55,5325,3309],{},". O ",[55,5328,696],{}," encontra espaço sobrando, então ",[294,5331,4697],{}," aloca nada: ele escreve ",[55,5334,471],{}," na posição 3 do array existente e devolve um cabeçalho com ",[55,5337,5338],{},"len=4"," apontando para o mesmo array.",[45,5341,5342,5343,5345,5346,5348,5349,5351,5352,5354],{},"Quando ",[55,5344,3264],{}," é chamado uma segunda vez a partir do mesmo pai, o cabeçalho de ",[55,5347,5321],{}," continua com ",[55,5350,3313],{}," — ele nunca mudou. O ",[55,5353,696],{}," escreve de novo na posição 3, por cima do middleware anterior. Os dois routers filhos terminam com cabeçalhos diferentes, mas apontando para o mesmo array, e o array só tem uma posição 3.",[45,5356,5357,5358,5360],{},"A correção é dizer ao ",[55,5359,696],{}," que ele não tem permissão para usar a capacidade sobrando:",[67,5362,5364],{"className":69,"code":5363,"language":71,"meta":72,"style":72},"func (r *Router) Group(m Middleware) *Router {\n    base := r.middlewares[:len(r.middlewares):len(r.middlewares)]\n    return &Router{middlewares: append(base, m)}\n}\n",[55,5365,5366,5396,5437,5464],{"__ignoreMap":72},[76,5367,5368,5370,5372,5374,5376,5378,5380,5382,5384,5386,5388,5390,5392,5394],{"class":78,"line":79},[76,5369,134],{"class":82},[76,5371,137],{"class":93},[76,5373,141],{"class":140},[76,5375,144],{"class":82},[76,5377,3182],{"class":86},[76,5379,150],{"class":93},[76,5381,3187],{"class":153},[76,5383,157],{"class":93},[76,5385,471],{"class":140},[76,5387,3194],{"class":86},[76,5389,150],{"class":93},[76,5391,726],{"class":82},[76,5393,3182],{"class":86},[76,5395,94],{"class":93},[76,5397,5398,5401,5403,5405,5407,5409,5411,5413,5415,5417,5419,5421,5424,5426,5428,5430,5432,5434],{"class":78,"line":97},[76,5399,5400],{"class":100},"    base ",[76,5402,524],{"class":82},[76,5404,201],{"class":100},[76,5406,179],{"class":93},[76,5408,3215],{"class":100},[76,5410,4498],{"class":93},[76,5412,500],{"class":153},[76,5414,157],{"class":93},[76,5416,3224],{"class":100},[76,5418,179],{"class":93},[76,5420,3215],{"class":100},[76,5422,5423],{"class":93},"):",[76,5425,500],{"class":153},[76,5427,157],{"class":93},[76,5429,3224],{"class":100},[76,5431,179],{"class":93},[76,5433,3215],{"class":100},[76,5435,5436],{"class":93},")]\n",[76,5438,5439,5441,5443,5445,5447,5449,5451,5453,5455,5458,5460,5462],{"class":78,"line":119},[76,5440,198],{"class":197},[76,5442,737],{"class":82},[76,5444,3182],{"class":86},[76,5446,742],{"class":93},[76,5448,3215],{"class":100},[76,5450,359],{"class":93},[76,5452,1129],{"class":153},[76,5454,157],{"class":93},[76,5456,5457],{"class":100},"base",[76,5459,518],{"class":93},[76,5461,527],{"class":100},[76,5463,765],{"class":93},[76,5465,5466],{"class":78,"line":125},[76,5467,122],{"class":93},[45,5469,5470,5471,5474,5475,5477],{},"Com ",[55,5472,5473],{},"cap == len",", o ",[55,5476,696],{}," é obrigado a alocar um array novo e copiar, e cada filho passa a ter o seu. A biblioteca padrão tem um nome para essa operação desde o Go 1.21:",[67,5479,5481],{"className":69,"code":5480,"language":71,"meta":72,"style":72},"\u002F\u002F Clip removes unused capacity from the slice, returning s[:len(s):len(s)].\nfunc Clip[S ~[]E, E any](s S) S\n",[55,5482,5483,5488],{"__ignoreMap":72},[76,5484,5485],{"class":78,"line":79},[76,5486,5487],{"class":486},"\u002F\u002F Clip removes unused capacity from the slice, returning s[:len(s):len(s)].\n",[76,5489,5490,5492,5495,5497,5500,5503,5505,5508,5510,5513,5516,5519,5521,5524,5526],{"class":78,"line":97},[76,5491,134],{"class":82},[76,5493,5494],{"class":153}," Clip",[76,5496,106],{"class":93},[76,5498,5499],{"class":140},"S",[76,5501,5502],{"class":82}," ~",[76,5504,3491],{"class":93},[76,5506,5507],{"class":86},"E",[76,5509,518],{"class":93},[76,5511,5512],{"class":140}," E",[76,5514,5515],{"class":86}," any",[76,5517,5518],{"class":93},"](",[76,5520,4054],{"class":140},[76,5522,5523],{"class":86}," S",[76,5525,150],{"class":93},[76,5527,5528],{"class":86}," S\n",[45,5530,5531],{},"Então a versão que eu escreveria de verdade é:",[67,5533,5535],{"className":69,"code":5534,"language":71,"meta":72,"style":72},"func (r *Router) Group(m Middleware) *Router {\n    return &Router{middlewares: append(slices.Clip(r.middlewares), m)}\n}\n",[55,5536,5537,5567,5606],{"__ignoreMap":72},[76,5538,5539,5541,5543,5545,5547,5549,5551,5553,5555,5557,5559,5561,5563,5565],{"class":78,"line":79},[76,5540,134],{"class":82},[76,5542,137],{"class":93},[76,5544,141],{"class":140},[76,5546,144],{"class":82},[76,5548,3182],{"class":86},[76,5550,150],{"class":93},[76,5552,3187],{"class":153},[76,5554,157],{"class":93},[76,5556,471],{"class":140},[76,5558,3194],{"class":86},[76,5560,150],{"class":93},[76,5562,726],{"class":82},[76,5564,3182],{"class":86},[76,5566,94],{"class":93},[76,5568,5569,5571,5573,5575,5577,5579,5581,5583,5585,5587,5589,5592,5594,5596,5598,5600,5602,5604],{"class":78,"line":97},[76,5570,198],{"class":197},[76,5572,737],{"class":82},[76,5574,3182],{"class":86},[76,5576,742],{"class":93},[76,5578,3215],{"class":100},[76,5580,359],{"class":93},[76,5582,1129],{"class":153},[76,5584,157],{"class":93},[76,5586,1317],{"class":100},[76,5588,179],{"class":93},[76,5590,5591],{"class":153},"Clip",[76,5593,157],{"class":93},[76,5595,3224],{"class":100},[76,5597,179],{"class":93},[76,5599,3215],{"class":100},[76,5601,865],{"class":93},[76,5603,527],{"class":100},[76,5605,765],{"class":93},[76,5607,5608],{"class":78,"line":119},[76,5609,122],{"class":93},[67,5611,5614],{"className":5612,"code":5613,"language":276,"meta":72},[274],"admin : [logging recover requestID requireAdmin]\npublic: [logging recover requestID rateLimit]\n",[55,5615,5613],{"__ignoreMap":72},[288,5617,5618],{},[45,5619,5620,5621,5624,5625,5628,5629,5631],{},"O custo dessa correção é uma alocação por chamada, e ela é o ponto. ",[55,5622,5623],{},"slices.Clip"," transforma \"às vezes compartilha, às vezes não\" em \"nunca compartilha\". A regra que eu uso: ",[294,5626,5627],{},"se um slice vai ser guardado em uma struct de vida longa e derivado depois, ele precisa ser clipado ou clonado na fronteira."," Pagar uma alocação previsível é melhor do que depender de um ",[55,5630,3931],{}," que ninguém está olhando.",[1301,5633,5635],{"id":5634},"o-mesmo-mecanismo-do-lado-de-dentro","O mesmo mecanismo, do lado de dentro",[45,5637,5638,5639,5641],{},"O aliasing não é só sobre ",[55,5640,696],{}," acidental. Ele também aparece em um padrão bastante recomendado — o filtro sem alocação:",[67,5643,5645],{"className":69,"code":5644,"language":71,"meta":72,"style":72},"\u002F\u002F activeOnly reaproveita o array de origem para não alocar.\nfunc activeOnly(orders []Order) []Order {\n    out := orders[:0]\n    for _, o := range orders {\n        if o.Status != \"cancelled\" {\n            out = append(out, o)\n        }\n    }\n    return out\n}\n",[55,5646,5647,5652,5675,5690,5708,5731,5750,5755,5759,5765],{"__ignoreMap":72},[76,5648,5649],{"class":78,"line":79},[76,5650,5651],{"class":486},"\u002F\u002F activeOnly reaproveita o array de origem para não alocar.\n",[76,5653,5654,5656,5659,5661,5663,5665,5667,5669,5671,5673],{"class":78,"line":97},[76,5655,134],{"class":82},[76,5657,5658],{"class":153}," activeOnly",[76,5660,157],{"class":93},[76,5662,4271],{"class":140},[76,5664,1068],{"class":93},[76,5666,2475],{"class":86},[76,5668,150],{"class":93},[76,5670,1068],{"class":93},[76,5672,2475],{"class":86},[76,5674,94],{"class":93},[76,5676,5677,5679,5681,5684,5686,5688],{"class":78,"line":119},[76,5678,1077],{"class":100},[76,5680,524],{"class":82},[76,5682,5683],{"class":100}," orders",[76,5685,4498],{"class":93},[76,5687,1006],{"class":217},[76,5689,538],{"class":93},[76,5691,5692,5694,5696,5698,5700,5702,5704,5706],{"class":78,"line":125},[76,5693,1107],{"class":197},[76,5695,1426],{"class":100},[76,5697,518],{"class":93},[76,5699,4317],{"class":100},[76,5701,524],{"class":82},[76,5703,568],{"class":197},[76,5705,4324],{"class":100},[76,5707,573],{"class":93},[76,5709,5710,5713,5715,5717,5719,5722,5724,5727,5729],{"class":78,"line":131},[76,5711,5712],{"class":197},"        if",[76,5714,4343],{"class":100},[76,5716,179],{"class":93},[76,5718,2520],{"class":100},[76,5720,5721],{"class":82},"!=",[76,5723,598],{"class":260},[76,5725,5726],{"class":264},"cancelled",[76,5728,261],{"class":260},[76,5730,94],{"class":93},[76,5732,5733,5736,5738,5740,5742,5744,5746,5748],{"class":78,"line":173},[76,5734,5735],{"class":100},"            out ",[76,5737,1126],{"class":82},[76,5739,1129],{"class":153},[76,5741,157],{"class":93},[76,5743,1134],{"class":100},[76,5745,518],{"class":93},[76,5747,4343],{"class":100},[76,5749,270],{"class":93},[76,5751,5752],{"class":78,"line":194},[76,5753,5754],{"class":93},"        }\n",[76,5756,5757],{"class":78,"line":221},[76,5758,1146],{"class":93},[76,5760,5761,5763],{"class":78,"line":581},[76,5762,198],{"class":197},[76,5764,1153],{"class":100},[76,5766,5767],{"class":78,"line":586},[76,5768,122],{"class":93},[45,5770,5771,5772,5774],{},"Ele funciona porque ",[55,5773,1134],{}," sempre escreve em uma posição que já foi lida. E ele destrói a entrada:",[67,5776,5779],{"className":5777,"code":5778,"language":276,"meta":72},[274],"active: [paid paid]\nall   : [paid paid paid]  \u003C- o chamador perdeu o pedido 2\n",[55,5780,5778],{"__ignoreMap":72},[45,5782,5783,5784,5787],{},"Não é um bug do padrão, é o preço dele. O problema aparece quando essa função mora em um pacote de utilidades, com um nome como ",[55,5785,5786],{},"FilterActive",", e quem chama não faz ideia. Se a função é pública, ou você documenta que ela consome a entrada, ou você não reaproveita o array:",[67,5789,5791],{"className":69,"code":5790,"language":71,"meta":72,"style":72},"func activeOnly(orders []Order) []Order {\n    return slices.DeleteFunc(slices.Clone(orders), func(o Order) bool {\n        return o.Status == \"cancelled\"\n    })\n}\n",[55,5792,5793,5815,5856,5874,5879],{"__ignoreMap":72},[76,5794,5795,5797,5799,5801,5803,5805,5807,5809,5811,5813],{"class":78,"line":79},[76,5796,134],{"class":82},[76,5798,5658],{"class":153},[76,5800,157],{"class":93},[76,5802,4271],{"class":140},[76,5804,1068],{"class":93},[76,5806,2475],{"class":86},[76,5808,150],{"class":93},[76,5810,1068],{"class":93},[76,5812,2475],{"class":86},[76,5814,94],{"class":93},[76,5816,5817,5819,5821,5823,5826,5828,5830,5832,5835,5837,5839,5841,5843,5845,5848,5850,5852,5854],{"class":78,"line":97},[76,5818,198],{"class":197},[76,5820,1398],{"class":100},[76,5822,179],{"class":93},[76,5824,5825],{"class":153},"DeleteFunc",[76,5827,157],{"class":93},[76,5829,1317],{"class":100},[76,5831,179],{"class":93},[76,5833,5834],{"class":153},"Clone",[76,5836,157],{"class":93},[76,5838,4271],{"class":100},[76,5840,865],{"class":93},[76,5842,1783],{"class":82},[76,5844,157],{"class":93},[76,5846,5847],{"class":140},"o",[76,5849,2442],{"class":86},[76,5851,150],{"class":93},[76,5853,168],{"class":109},[76,5855,94],{"class":93},[76,5857,5858,5860,5862,5864,5866,5868,5870,5872],{"class":78,"line":119},[76,5859,4809],{"class":197},[76,5861,4343],{"class":100},[76,5863,179],{"class":93},[76,5865,2520],{"class":100},[76,5867,387],{"class":82},[76,5869,598],{"class":260},[76,5871,5726],{"class":264},[76,5873,2530],{"class":260},[76,5875,5876],{"class":78,"line":125},[76,5877,5878],{"class":93},"    })\n",[76,5880,5881],{"class":78,"line":131},[76,5882,122],{"class":93},[1301,5884,5886],{"id":5885},"segurar-um-pedaço-é-segurar-o-array-inteiro","Segurar um pedaço é segurar o array inteiro",[45,5888,5889],{},"A última forma de aliasing é a que não corrompe nada, só consome memória. Como o slice guarda um ponteiro para o array, o coletor de lixo não pode liberar o array enquanto qualquer slice apontar para ele — mesmo que esse slice descreva 16 bytes de um array de 10 MiB.",[45,5891,5892],{},"O blog oficial já alertava para isso:",[326,5894,5895,5898],{},[45,5896,5897],{},"\"refatiar um slice não faz uma cópia do array subjacente. O array completo será mantido em memória até que ele não seja mais referenciado.\"",[45,5899,336,5900],{},[49,5901,4000,5904],{"href":5902,"rel":5903},"https:\u002F\u002Fgo.dev\u002Fblog\u002Fslices-intro",[341],[344,5905,5906],{},"Go Slices: usage and internals",[45,5908,5909],{},"Um caso concreto: uma função que lê um payload e guarda só um identificador dele.",[67,5911,5913],{"className":69,"code":5912,"language":71,"meta":72,"style":72},"func extractID(payload []byte) []byte {\n    return payload[:16] \u002F\u002F mantém o payload inteiro vivo\n}\n\nfunc extractIDSafe(payload []byte) []byte {\n    return slices.Clone(payload[:16]) \u002F\u002F 16 bytes, e só\n}\n",[55,5914,5915,5939,5956,5960,5964,5987,6010],{"__ignoreMap":72},[76,5916,5917,5919,5922,5924,5927,5929,5931,5933,5935,5937],{"class":78,"line":79},[76,5918,134],{"class":82},[76,5920,5921],{"class":153}," extractID",[76,5923,157],{"class":93},[76,5925,5926],{"class":140},"payload",[76,5928,1068],{"class":93},[76,5930,852],{"class":109},[76,5932,150],{"class":93},[76,5934,1068],{"class":93},[76,5936,852],{"class":109},[76,5938,94],{"class":93},[76,5940,5941,5943,5946,5948,5951,5953],{"class":78,"line":97},[76,5942,198],{"class":197},[76,5944,5945],{"class":100}," payload",[76,5947,4498],{"class":93},[76,5949,5950],{"class":217},"16",[76,5952,113],{"class":93},[76,5954,5955],{"class":486}," \u002F\u002F mantém o payload inteiro vivo\n",[76,5957,5958],{"class":78,"line":119},[76,5959,122],{"class":93},[76,5961,5962],{"class":78,"line":125},[76,5963,128],{"emptyLinePlaceholder":33},[76,5965,5966,5968,5971,5973,5975,5977,5979,5981,5983,5985],{"class":78,"line":131},[76,5967,134],{"class":82},[76,5969,5970],{"class":153}," extractIDSafe",[76,5972,157],{"class":93},[76,5974,5926],{"class":140},[76,5976,1068],{"class":93},[76,5978,852],{"class":109},[76,5980,150],{"class":93},[76,5982,1068],{"class":93},[76,5984,852],{"class":109},[76,5986,94],{"class":93},[76,5988,5989,5991,5993,5995,5997,5999,6001,6003,6005,6007],{"class":78,"line":173},[76,5990,198],{"class":197},[76,5992,1398],{"class":100},[76,5994,179],{"class":93},[76,5996,5834],{"class":153},[76,5998,157],{"class":93},[76,6000,5926],{"class":100},[76,6002,4498],{"class":93},[76,6004,5950],{"class":217},[76,6006,483],{"class":93},[76,6008,6009],{"class":486}," \u002F\u002F 16 bytes, e só\n",[76,6011,6012],{"class":78,"line":194},[76,6013,122],{"class":93},[45,6015,6016],{},"Guardando 20 desses identificadores, com payloads de 10 MiB:",[67,6018,6021],{"className":6019,"code":6020,"language":276,"meta":72},[274],"20 fatias de 16 bytes (sub-slice):    heap = 200.2 MiB\n20 fatias de 16 bytes (slices.Clone): heap =   0.2 MiB\n",[55,6022,6020],{"__ignoreMap":72},[45,6024,6025],{},"Não há vazamento no sentido clássico — tudo será liberado quando os 20 slices morrerem. Mas enquanto eles viverem, o processo carrega 200 MiB para guardar 320 bytes de informação útil. É o tipo de coisa que aparece como \"uso de memória alto e estável\" e não como um leak crescente, o que torna o diagnóstico bem mais chato.",[985,6027,6028],{},[45,6029,6030,6031,6033,6034,6037],{},"O mesmo vale para ",[55,6032,110],{},", que também é um ponteiro mais um comprimento. Fatiar uma string enorme e guardar o pedaço mantém a original viva. A saída é ",[55,6035,6036],{},"strings.Clone",", do Go 1.18.",[315,6039,2422],{"id":2421},[45,6041,6042,6047,6048,619,6051,2780,6053,6055,6056,6059,6060,6062,6063,6065],{},[294,6043,4367,6044,6046],{},[55,6045,696],{}," compartilha ou copia, e você não controla qual."," Esta é a armadilha-mãe, e vale enunciá-la de forma explícita: depois de ",[55,6049,6050],{},"b := append(a, x)",[55,6052,49],{},[55,6054,1596],{}," podem ou não compartilhar o array, dependendo de ",[55,6057,6058],{},"cap(a)",". Não existe um jeito de perguntar \"esse ",[55,6061,696],{}," alocou?\" que seja útil em produção. Trate o resultado do ",[55,6064,696],{}," como um slice novo e o original como inválido, ou clipe antes.",[45,6067,6068,417,6071,6074,6075,6077,6078,6080],{},[294,6069,6070],{},"Encurtar um slice não libera nada.",[55,6072,6073],{},"s = s[:0]"," zera o ",[55,6076,500],{}," e mantém a capacidade — o que é ótimo para reaproveitar buffers e péssimo se os elementos forem ponteiros, porque o array continua referenciando os objetos. Se o elemento contém ponteiros e o slice tem vida longa, zere a cauda ou use as funções do pacote ",[55,6079,1317],{},", que fazem isso desde o Go 1.22:",[67,6082,6085],{"className":6083,"code":6084,"language":276,"meta":72},[274],"remoção manual com append: slot além do len = &{d}   \u003C- ainda referenciado\nslices.Delete:             slot além do len = \u003Cnil>  \u003C- zerado\n",[55,6086,6084],{"__ignoreMap":72},[45,6088,6089,6095,6096,6099,6100,6102],{},[294,6090,6091,6094],{},[55,6092,6093],{},"slices.Clone"," é uma cópia rasa."," A documentação diz que \"os elementos são copiados por atribuição\". Clonar um ",[55,6097,6098],{},"[]*Order"," te dá um array novo de ponteiros para os mesmos ",[55,6101,2475],{},". Se o objetivo era isolamento de verdade, o clone raso não resolve.",[45,6104,6105],{},[294,6106,6107,6109],{},[55,6108,61],{}," e slice vazio não são a mesma coisa, exceto quando são.",[67,6111,6113],{"className":69,"code":6112,"language":71,"meta":72,"style":72},"var nilSlice []int\nemptySlice := []int{}\n\nnilSlice == nil   \u002F\u002F true\nemptySlice == nil \u002F\u002F false\nlen(nilSlice) == len(emptySlice) \u002F\u002F true\nslices.Equal(nilSlice, emptySlice) \u002F\u002F true\n",[55,6114,6115,6126,6139,6143,6155,6165,6190],{"__ignoreMap":72},[76,6116,6117,6119,6122,6124],{"class":78,"line":79},[76,6118,239],{"class":82},[76,6120,6121],{"class":100}," nilSlice ",[76,6123,3491],{"class":93},[76,6125,116],{"class":109},[76,6127,6128,6131,6133,6135,6137],{"class":78,"line":97},[76,6129,6130],{"class":100},"emptySlice ",[76,6132,524],{"class":82},[76,6134,1068],{"class":93},[76,6136,762],{"class":109},[76,6138,2655],{"class":93},[76,6140,6141],{"class":78,"line":119},[76,6142,128],{"emptyLinePlaceholder":33},[76,6144,6145,6148,6150,6152],{"class":78,"line":125},[76,6146,6147],{"class":100},"nilSlice ",[76,6149,387],{"class":82},[76,6151,895],{"class":894},[76,6153,6154],{"class":486},"   \u002F\u002F true\n",[76,6156,6157,6159,6161,6163],{"class":78,"line":131},[76,6158,6130],{"class":100},[76,6160,387],{"class":82},[76,6162,895],{"class":894},[76,6164,900],{"class":486},[76,6166,6167,6169,6171,6174,6176,6179,6181,6183,6186,6188],{"class":78,"line":173},[76,6168,500],{"class":153},[76,6170,157],{"class":93},[76,6172,6173],{"class":100},"nilSlice",[76,6175,150],{"class":93},[76,6177,6178],{"class":82}," ==",[76,6180,1095],{"class":153},[76,6182,157],{"class":93},[76,6184,6185],{"class":100},"emptySlice",[76,6187,150],{"class":93},[76,6189,969],{"class":486},[76,6191,6192,6194,6196,6199,6201,6203,6205,6208,6210],{"class":78,"line":194},[76,6193,1317],{"class":100},[76,6195,179],{"class":93},[76,6197,6198],{"class":153},"Equal",[76,6200,157],{"class":93},[76,6202,6173],{"class":100},[76,6204,518],{"class":93},[76,6206,6207],{"class":100}," emptySlice",[76,6209,150],{"class":93},[76,6211,969],{"class":486},[45,6213,6214,619,6216,619,6218,2780,6220,6222,6223,6225,6226,6228,6229,6231,6232,2780,6234,6236],{},[55,6215,696],{},[55,6217,500],{},[55,6219,3931],{},[55,6221,622],{}," tratam os dois igual, então na maior parte do código a diferença não importa. Ela importa em dois lugares: comparações explícitas contra ",[55,6224,61],{}," e serialização. O ",[55,6227,982],{}," gera ",[55,6230,2704],{}," para um slice ",[55,6233,61],{},[55,6235,3491],{}," para um vazio, e essa diferença já quebrou muito cliente. Se a sua API devolve listas, garanta o slice vazio.",[45,6238,6239,6244],{},[294,6240,6241,6243],{},[55,6242,622],{}," copia o elemento."," Não é específico de slices, mas é onde mais aparece:",[67,6246,6248],{"className":69,"code":6247,"language":71,"meta":72,"style":72},"for _, o := range orders {\n    o.Status = \"paid\" \u002F\u002F altera a cópia, não o slice\n}\nfor i := range orders {\n    orders[i].Status = \"paid\" \u002F\u002F altera o slice\n}\n",[55,6249,6250,6268,6288,6292,6306,6331],{"__ignoreMap":72},[76,6251,6252,6254,6256,6258,6260,6262,6264,6266],{"class":78,"line":79},[76,6253,565],{"class":197},[76,6255,1426],{"class":100},[76,6257,518],{"class":93},[76,6259,4317],{"class":100},[76,6261,524],{"class":82},[76,6263,568],{"class":197},[76,6265,4324],{"class":100},[76,6267,573],{"class":93},[76,6269,6270,6273,6275,6277,6279,6281,6283,6285],{"class":78,"line":97},[76,6271,6272],{"class":100},"    o",[76,6274,179],{"class":93},[76,6276,2520],{"class":100},[76,6278,1126],{"class":82},[76,6280,598],{"class":260},[76,6282,2527],{"class":264},[76,6284,261],{"class":260},[76,6286,6287],{"class":486}," \u002F\u002F altera a cópia, não o slice\n",[76,6289,6290],{"class":78,"line":119},[76,6291,122],{"class":93},[76,6293,6294,6296,6298,6300,6302,6304],{"class":78,"line":125},[76,6295,565],{"class":197},[76,6297,1798],{"class":100},[76,6299,524],{"class":82},[76,6301,568],{"class":197},[76,6303,4324],{"class":100},[76,6305,573],{"class":93},[76,6307,6308,6311,6313,6316,6318,6320,6322,6324,6326,6328],{"class":78,"line":131},[76,6309,6310],{"class":100},"    orders",[76,6312,106],{"class":93},[76,6314,6315],{"class":100},"i",[76,6317,2517],{"class":93},[76,6319,2520],{"class":100},[76,6321,1126],{"class":82},[76,6323,598],{"class":260},[76,6325,2527],{"class":264},[76,6327,261],{"class":260},[76,6329,6330],{"class":486}," \u002F\u002F altera o slice\n",[76,6332,6333],{"class":78,"line":173},[76,6334,122],{"class":93},[45,6336,6337,6338,6341],{},"Para elementos grandes isso também tem custo de cópia, e é um dos poucos casos em que ",[55,6339,6340],{},"for i := range"," é preferível por performance, não só por semântica.",[45,6343,6344,417,6347,6350,6351,6354,6355,6357],{},[294,6345,6346],{},"Reservar capacidade demais é um custo, não um seguro.",[55,6348,6349],{},"make([]T, 0, 1_000_000)"," aloca o array inteiro na hora, mesmo que você use dez posições. ",[55,6352,6353],{},"slices.Grow"," sofre do mesmo, e é por isso que ele é a ferramenta certa só quando você tem uma estimativa razoável. Quando não tem, deixe o ",[55,6356,696],{}," crescer: o algoritmo geométrico já é uma boa aposta.",[45,6359,6360,6363,6364,6367,6368,6371,6372,6375],{},[294,6361,6362],{},"Não otimize capacidade sem medir."," Vale aqui o mesmo critério do artigo sobre escape analysis. ",[55,6365,6366],{},"allocs\u002Fop"," só importa em caminhos quentes, e a diferença entre 10 alocações e 1 em uma função chamada três vezes por requisição não paga a complexidade de calcular o tamanho certo. Comece pelo ",[55,6369,6370],{},"-benchmem"," e pelo ",[55,6373,6374],{},"pprof",", não pelo código.",[315,6377,2732],{"id":2731},[45,6379,6380],{},"Boa parte do que está aqui é comportamento observável, não contrato, e mudou ao longo do tempo:",[2734,6382,6383,6391],{},[2737,6384,6385],{},[2740,6386,6387,6389],{},[2743,6388,2745],{},[2743,6390,2748],{},[2750,6392,6393,6404,6432,6456,6476],{},[2740,6394,6395,6398],{},[2755,6396,6397],{},"Go 1.18",[2755,6399,6400,6401,179],{},"A fórmula de crescimento passou a ser suave: em vez de dobrar até 1024 e depois multiplicar por 1,25, o fator começa a cair em 256. As release notes dizem apenas que \"a nova fórmula é menos propensa a transições bruscas no comportamento de alocação\"; os detalhes estão no commit ",[55,6402,6403],{},"2dda92f",[2740,6405,6406,6408],{},[2755,6407,2768],{},[2755,6409,6410,6411,6413,6414,619,6416,619,6418,619,6421,619,6424,619,6426,6429,6430,179],{},"O pacote ",[55,6412,1317],{}," entrou na biblioteca padrão, com ",[55,6415,5834],{},[55,6417,5591],{},[55,6419,6420],{},"Grow",[55,6422,6423],{},"Delete",[55,6425,6198],{},[55,6427,6428],{},"Insert"," e companhia. Antes disso, cada projeto tinha a sua versão de ",[55,6431,5591],{},[2740,6433,6434,6437],{},[2755,6435,6436],{},"Go 1.22",[2755,6438,6439,619,6441,619,6443,619,6446,2780,6449,6452,6453,179],{},[55,6440,6423],{},[55,6442,5825],{},[55,6444,6445],{},"Compact",[55,6447,6448],{},"CompactFunc",[55,6450,6451],{},"Replace"," passaram a zerar os elementos entre o novo e o antigo comprimento, resolvendo a retenção de ponteiros na cauda. Também chegou ",[55,6454,6455],{},"slices.Concat",[2740,6457,6458,6461],{},[2755,6459,6460],{},"Go 1.25",[2755,6462,6463,6464,6466,6467,6469,6470,6472,6473,179],{},"O compilador passou a alocar o array subjacente de slices na stack em mais situações (\"Faster slices\" nas release notes). É o que faz o primeiro ",[55,6465,696],{}," em um ",[55,6468,4965],{}," local devolver ",[55,6471,3309],{},". Pode ser desligado com ",[55,6474,6475],{},"-gcflags=all=-d=variablemakehash=n",[2740,6477,6478,6480],{},[2755,6479,296],{},[2755,6481,6482,6483,179],{},"A mesma otimização foi estendida a mais casos. Nada mudou na semântica; mudou o que você observa em ",[55,6484,3931],{},[45,6486,6487,6488,6490,6491,6494,6495,6497,6498,6501],{},"Se você mantém código que nasceu antes do Go 1.21, vale uma busca por implementações caseiras de ",[55,6489,5591],{}," e por remoções feitas com ",[55,6492,6493],{},"append(s[:i], s[i+1:]...)",". As primeiras podem ser trocadas por ",[55,6496,5623],{},"; as segundas provavelmente estão segurando ponteiros na cauda, e ",[55,6499,6500],{},"slices.Delete"," resolve.",[315,6503,2858],{"id":2857},[45,6505,2861],{},[2863,6507,6508,6514,6529,6539,6551],{},[2866,6509,6510,6513],{},[294,6511,6512],{},"Eu preciso mesmo de um array?"," Quase sempre não. Array faz sentido quando o tamanho é fixo por natureza (um hash de 32 bytes, um par método\u002Frota como chave de map) ou quando você quer comparabilidade. Fora isso, slice.",[2866,6515,6516,6519,6520,6522,6523,6525,6526,6528],{},[294,6517,6518],{},"Esse slice vai ser guardado em algum lugar que sobrevive à função?"," Se vai, decida na fronteira: ",[55,6521,6093],{}," se você precisa de dados próprios, ",[55,6524,5623],{}," se você só quer impedir que um ",[55,6527,696],{}," futuro pise em memória alheia.",[2866,6530,6531,6534,6535,6538],{},[294,6532,6533],{},"Eu sei quantos elementos vão entrar?"," Se sei, ",[55,6536,6537],{},"make([]T, 0, n)"," — com o zero no meio. Se não sei, deixe crescer.",[2866,6540,6541,6544,6545,6547,6548,6550],{},[294,6542,6543],{},"Essa função muta o slice que recebeu?"," Se muta, isso faz parte do contrato dela e precisa estar documentado. \"Recebe ",[55,6546,3902],{}," e devolve ",[55,6549,3902],{},"\" não diz se a entrada continua válida.",[2866,6552,6553,6556],{},[294,6554,6555],{},"Estou guardando um pedaço de algo grande?"," Se estou, clone. O array subjacente inteiro fica vivo enquanto qualquer fatia dele existir.",[45,6558,6559,6560,2780,6562,6564,6565,6567],{},"O que eu levaria deste artigo é que quase todo problema com slices vem de tratar o cabeçalho como se fosse o dado. Ele não é: ele é uma vista sobre um dado que mora em outro lugar e pode ser visto por mais gente. ",[55,6561,500],{},[55,6563,3931],{}," diferentes não são um detalhe de eficiência, são a informação de que existe memória ali que o seu slice não está mostrando — e que o próximo ",[55,6566,696],{}," pode usar.",[45,6569,6570,6571,6574,6575,6577],{},"Nos ",[49,6572,6573],{"href":51},"zero values"," o ganho era não precisar lembrar de inicializar. No ",[49,6576,5184],{"href":5183}," era não precisar decidir onde as coisas moram. Com slices o acordo é diferente: Go te dá uma abstração barata sobre memória compartilhada e, em troca, espera que você saiba que ela é compartilhada. É a única das três em que a linguagem não decide por você.",[315,6579,2949],{"id":2948},[2951,6581,6582,6589,6596,6606,6616,6624,6634,6641,6658,6668,6680,6691,6701,6710,6728,6739,6747,6764],{},[2866,6583,6584,6588],{},[49,6585,6587],{"href":3392,"rel":6586},[341],"The Go Programming Language Specification — Slice types"," — a definição de slice como descritor e a frase sobre compartilhamento de armazenamento.",[2866,6590,6591,6595],{},[49,6592,6594],{"href":3359,"rel":6593},[341],"The Go Programming Language Specification — Array types"," — o tamanho como parte do tipo.",[2866,6597,6598,6602,6603,6605],{},[49,6599,6601],{"href":4594,"rel":6600},[341],"The Go Programming Language Specification — Full slice expressions"," — a sintaxe ",[55,6604,4581],{}," e como ela define a capacidade.",[2866,6607,6608,6612,6613,6615],{},[49,6609,6611],{"href":4717,"rel":6610},[341],"The Go Programming Language Specification — Appending to and copying slices"," — o que ",[55,6614,696],{}," garante, e o que ele não garante.",[2866,6617,6618,6623],{},[49,6619,6622],{"href":6620,"rel":6621},"https:\u002F\u002Fgo.dev\u002Fref\u002Fspec#Comparison_operators",[341],"The Go Programming Language Specification — Comparison operators"," — por que arrays são comparáveis e slices não.",[2866,6625,6626,6630,6631,6633],{},[49,6627,6629],{"href":3998,"rel":6628},[341],"Go Blog — Arrays, slices (and strings): The mechanics of 'append'"," — a explicação canônica do cabeçalho de três palavras e de por que ",[55,6632,696],{}," devolve um valor.",[2866,6635,6636,6640],{},[49,6637,6639],{"href":5902,"rel":6638},[341],"Go Blog — Go Slices: usage and internals"," — o alerta sobre refatiar manter o array inteiro vivo.",[2866,6642,6643,3098,6649,2780,6651,6653,6654,6657],{},[49,6644,6647],{"href":6645,"rel":6646},"https:\u002F\u002Fgithub.com\u002Fgolang\u002Fgo\u002Fblob\u002Fmaster\u002Fsrc\u002Fruntime\u002Fslice.go",[341],[55,6648,4733],{},[55,6650,5179],{},[55,6652,4730],{},", com o ",[55,6655,6656],{},"threshold = 256"," e a fórmula de transição.",[2866,6659,6660,6667],{},[49,6661,6664],{"href":6662,"rel":6663},"https:\u002F\u002Fgithub.com\u002Fgolang\u002Fgo\u002Fblob\u002Fmaster\u002Fsrc\u002Finternal\u002Fruntime\u002Fgc\u002Fsizeclasses.go",[341],[55,6665,6666],{},"internal\u002Fruntime\u002Fgc\u002Fsizeclasses.go"," — a tabela de size classes que explica por que a capacidade observada não bate com a fórmula.",[2866,6669,6670,3098,6676,6679],{},[49,6671,6674],{"href":6672,"rel":6673},"https:\u002F\u002Fgithub.com\u002Fgolang\u002Fgo\u002Fblob\u002Fmaster\u002Fsrc\u002Fcmd\u002Fcompile\u002Finternal\u002Fbase\u002Fflag.go",[341],[55,6675,5192],{},[55,6677,6678],{},"VariableMakeThreshold = 32",", o tamanho do buffer na stack para slices que não escapam.",[2866,6681,6682,6690],{},[49,6683,6686,6687,6689],{"href":6684,"rel":6685},"https:\u002F\u002Fgithub.com\u002Fgolang\u002Fgo\u002Fcommit\u002F2dda92ff6f9f07eeb110ecbf0fc2d7a0ddd27f9d",[341],"commit ",[55,6688,6403],{}," — runtime: make slice growth formula a bit smoother"," — a mudança da fórmula de crescimento e a justificativa do limite de 256.",[2866,6692,6693,6698,6699,179],{},[49,6694,6697],{"href":6695,"rel":6696},"https:\u002F\u002Fgo.dev\u002Fdoc\u002Fgo1.18",[341],"Go 1.18 Release Notes"," — a menção oficial à nova fórmula do ",[55,6700,696],{},[2866,6702,6703,6706,6707,6709],{},[49,6704,3036],{"href":3034,"rel":6705},[341]," — a entrada do pacote ",[55,6708,1317],{}," na biblioteca padrão.",[2866,6711,6712,6717,6718,619,6720,2780,6722,6724,6725,179],{},[49,6713,6716],{"href":6714,"rel":6715},"https:\u002F\u002Fgo.dev\u002Fdoc\u002Fgo1.22",[341],"Go 1.22 Release Notes"," — o zeramento da cauda em ",[55,6719,6423],{},[55,6721,6445],{},[55,6723,6451],{},", e a chegada de ",[55,6726,6727],{},"Concat",[2866,6729,6730,6735,6736,179],{},[49,6731,6734],{"href":6732,"rel":6733},"https:\u002F\u002Fgo.dev\u002Fdoc\u002Fgo1.25",[341],"Go 1.25 Release Notes — Faster slices"," — a alocação do array subjacente na stack e a flag ",[55,6737,6738],{},"variablemakehash",[2866,6740,6741,6746],{},[49,6742,6745],{"href":6743,"rel":6744},"https:\u002F\u002Fgo.dev\u002Fdoc\u002Fgo1.26",[341],"Go 1.26 Release Notes — Compiler"," — a extensão da mesma otimização.",[2866,6748,6749,3098,6755,619,6757,619,6759,619,6761,6763],{},[49,6750,1520,6753],{"href":6751,"rel":6752},"https:\u002F\u002Fpkg.go.dev\u002Fslices",[341],[55,6754,1317],{},[55,6756,5834],{},[55,6758,5591],{},[55,6760,6420],{},[55,6762,6423],{}," e o contrato de cada uma, incluindo a cópia rasa.",[2866,6765,6766,6772,6773,6776,6777,179],{},[49,6767,6770],{"href":6768,"rel":6769},"https:\u002F\u002Fgithub.com\u002Fashanbrown\u002Fmakezero",[341],[55,6771,4427],{}," — o linter que pega ",[55,6774,6775],{},"make([]T, n)"," seguido de ",[55,6778,696],{},[3129,6780,6781],{},"html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":72,"searchDepth":97,"depth":97,"links":6783},[6784,6785,6786,6791,6795,6799,6800,6801,6802],{"id":3334,"depth":97,"text":3335},{"id":3976,"depth":97,"text":3977},{"id":4237,"depth":97,"text":6787,"children":6788},"Parte 2: len não é cap",[6789,6790],{"id":4437,"depth":119,"text":4438},{"id":4571,"depth":119,"text":4572},{"id":4690,"depth":97,"text":4691,"children":6792},[6793,6794],{"id":4948,"depth":119,"text":4949},{"id":5086,"depth":119,"text":5087},{"id":5244,"depth":97,"text":5245,"children":6796},[6797,6798],{"id":5634,"depth":119,"text":5635},{"id":5885,"depth":119,"text":5886},{"id":2421,"depth":97,"text":2422},{"id":2731,"depth":97,"text":2732},{"id":2857,"depth":97,"text":2858},{"id":2948,"depth":97,"text":2949},"2026-07-27","Como slices funcionam por dentro em Go: o cabeçalho de três palavras, a diferença entre len e cap, como o append faz o slice crescer, e por que dois slices podem enxergar o mesmo array sem você perceber.",{},22,{"title":10,"description":6804},"qHAFweD2FOxqucnfpAZQJLsolSB42YJ2kbloaVRJqvA",{"id":6810,"title":14,"author":29,"body":6811,"date":10430,"description":10431,"extension":3148,"image":3149,"meta":10432,"minRead":7562,"navigation":33,"path":15,"seo":10433,"stem":16,"tags":29,"updatedAt":29,"__hash__":10434},"blog\u002Fblog\u002Fescape-analysis-em-go-stack-ou-heap.md",{"type":42,"value":6812,"toc":10410},[6813,6818,6821,6879,6882,6930,6936,6939,6948,6952,6955,6966,6972,6978,6987,6991,7010,7013,7027,7030,7035,7042,7045,7059,7072,7079,7246,7252,7266,7273,7280,7301,7304,7617,7620,7626,7636,7639,7691,7710,7714,7720,8029,8032,8038,8041,8092,8111,8115,8118,8122,8133,8140,8319,8325,8338,8344,8357,8361,8371,8374,8605,8618,8624,8665,8683,8689,8934,8940,8968,8985,8990,8994,8997,9010,9017,9021,9024,9283,9289,9301,9313,9331,9335,9345,9348,9391,9593,9605,9611,9624,9628,9634,9637,9648,9678,9684,9734,9740,9755,9768,9771,9773,9776,9788,10019,10025,10038,10046,10056,10069,10078,10091,10103,10105,10108,10160,10167,10169,10171,10234,10240,10242,10407],[45,6814,47,6815,6817],{},[49,6816,52],{"href":51}," eu terminei falando que, em Go, tudo é passado por valor, inclusive ponteiros. Ficou faltando responder uma pergunta que aparece logo em seguida: se eu pego o endereço de uma variável local e devolvo esse endereço, o que impede o dado de sumir quando a função retorna?",[45,6819,6820],{},"Em C, isso é um bug clássico:",[67,6822,6825],{"className":6823,"code":6824,"language":870,"meta":72,"style":72},"language-c shiki shiki-themes material-theme-lighter github-light github-dark","int *broken(void) {\n    int x = 42;\n    return &x; \u002F\u002F ponteiro pendurado: x morre com o frame\n}\n",[55,6826,6827,6845,6861,6875],{"__ignoreMap":72},[76,6828,6829,6831,6833,6836,6838,6841,6843],{"class":78,"line":79},[76,6830,762],{"class":109},[76,6832,726],{"class":82},[76,6834,6835],{"class":153},"broken",[76,6837,157],{"class":93},[76,6839,6840],{"class":109},"void",[76,6842,150],{"class":93},[76,6844,94],{"class":93},[76,6846,6847,6850,6854,6856,6858],{"class":78,"line":97},[76,6848,6849],{"class":109},"    int",[76,6851,6853],{"class":6852},"skxfh"," x ",[76,6855,1126],{"class":82},[76,6857,1354],{"class":217},[76,6859,6860],{"class":93},";\n",[76,6862,6863,6865,6867,6870,6872],{"class":78,"line":119},[76,6864,198],{"class":197},[76,6866,737],{"class":82},[76,6868,6869],{"class":6852},"x",[76,6871,1805],{"class":93},[76,6873,6874],{"class":486}," \u002F\u002F ponteiro pendurado: x morre com o frame\n",[76,6876,6877],{"class":78,"line":125},[76,6878,122],{"class":93},[45,6880,6881],{},"Em Go, o equivalente é código idiomático e aparece em praticamente todo construtor que você já escreveu:",[67,6883,6885],{"className":69,"code":6884,"language":71,"meta":72,"style":72},"func NewCounter() *Counter {\n    c := Counter{}\n    return &c \u002F\u002F isso é normal em Go\n}\n",[55,6886,6887,6903,6915,6926],{"__ignoreMap":72},[76,6888,6889,6891,6894,6896,6898,6901],{"class":78,"line":79},[76,6890,134],{"class":82},[76,6892,6893],{"class":153}," NewCounter",[76,6895,723],{"class":93},[76,6897,726],{"class":82},[76,6899,6900],{"class":86},"Counter",[76,6902,94],{"class":93},[76,6904,6905,6908,6910,6913],{"class":78,"line":97},[76,6906,6907],{"class":100},"    c ",[76,6909,524],{"class":82},[76,6911,6912],{"class":86}," Counter",[76,6914,2655],{"class":93},[76,6916,6917,6919,6921,6923],{"class":78,"line":119},[76,6918,198],{"class":197},[76,6920,737],{"class":82},[76,6922,2135],{"class":100},[76,6924,6925],{"class":486},"\u002F\u002F isso é normal em Go\n",[76,6927,6928],{"class":78,"line":125},[76,6929,122],{"class":93},[45,6931,6932,6933,6935],{},"Os dois programas parecem fazer a mesma coisa, mas só um deles funciona. A diferença é uma etapa do compilador chamada ",[294,6934,5184],{},", e ela é o que permite que Go tenha ponteiros sem ter as armadilhas de ponteiros.",[45,6937,6938],{},"A proposta deste artigo é ir do porquê ao como: primeiro por que stack e heap custam diferente, depois como o compilador decide entre as duas, como enxergar essa decisão com suas próprias ferramentas, quais padrões de código realmente causam escape e, no fim, o que fazer (e o que não fazer) com essa informação.",[288,6940,6941],{},[45,6942,292,6943,3327,6945,6947],{},[294,6944,296],{},[55,6946,3330],{}," (o compilador oficial). Números concretos, como limites de tamanho, são detalhes de implementação e já mudaram entre versões; existe uma seção sobre isso mais adiante.",[315,6949,6951],{"id":6950},"duas-regiões-de-memória-dois-custos-bem-diferentes","Duas regiões de memória, dois custos bem diferentes",[45,6953,6954],{},"Antes de falar da decisão, vale entender por que ela importa.",[45,6956,6957,6958,6961,6962,6965],{},"A ",[294,6959,6960],{},"stack"," é uma região por goroutine. Ela funciona em LIFO: chamou função, empilhou um frame; retornou, desempilhou. Alocar espaço na stack é somar um número a um registrador, e liberar é subtrair. Não existe fragmentação, não existe busca por espaço livre e, principalmente, ",[294,6963,6964],{},"o coletor de lixo não precisa saber nada sobre isso",". Quando a função retorna, a memória já era.",[45,6967,4367,6968,6971],{},[294,6969,6970],{},"heap"," é uma região compartilhada por todo o processo. Ele existe justamente para dados cujo tempo de vida não se encaixa no ciclo de vida de uma chamada de função. O preço é que alguém precisa descobrir quando aquele dado pode ser liberado, e esse alguém é o garbage collector. Cada objeto no heap é trabalho de marcação e varredura para o GC.",[45,6973,6974,6975],{},"Então a conta é essa: ",[294,6976,6977],{},"stack é barata na alocação e de graça no GC. Heap é mais cara na alocação e, pior, continua custando depois.",[985,6979,6980],{},[45,6981,6982,6983,6986],{},"Um detalhe que costuma surpreender: as stacks das goroutines em Go não têm tamanho fixo. Cada goroutine começa com uma stack pequena (2 KiB, definidos pela constante ",[55,6984,6985],{},"stackMin"," no runtime) e o runtime a faz crescer copiando para uma área maior quando necessário, até um teto de 1 GB em plataformas de 64 bits. É por isso que criar centenas de milhares de goroutines é viável.",[315,6988,6990],{"id":6989},"parte-1-quem-decide-é-o-compilador-não-você","Parte 1: quem decide é o compilador, não você",[45,6992,6993,6994,6997,6998,7001,7002,7005,7006,7009],{},"Aqui está o ponto que reorganiza o entendimento de quem vem de C ou C++: em Go, ",[294,6995,6996],{},"não existe sintaxe para escolher entre stack e heap",". ",[55,6999,7000],{},"new"," não significa heap. ",[55,7003,7004],{},"&T{}"," não significa heap. Uma variável sem ",[55,7007,7008],{},"&"," não significa stack.",[45,7011,7012],{},"A própria FAQ oficial é direta sobre isso:",[288,7014,7015,7018],{},[45,7016,7017],{},"\"Do ponto de vista da correção, você não precisa saber. Cada variável em Go existe enquanto houver referências a ela. O local de armazenamento escolhido pela implementação é irrelevante para a semântica da linguagem.\"",[45,7019,336,7020],{},[49,7021,1699,7024],{"href":7022,"rel":7023},"https:\u002F\u002Fgo.dev\u002Fdoc\u002Ffaq#stack_or_heap",[341],[344,7025,7026],{},"How do I know whether a variable is allocated on the heap or the stack?",[45,7028,7029],{},"E o mesmo texto explica o critério:",[326,7031,7032],{},[45,7033,7034],{},"\"se o compilador não conseguir provar que a variável não é referenciada depois que a função retorna, então o compilador precisa alocar a variável no heap garantido pelo coletor de lixo, para evitar erros de ponteiro pendurado.\"",[45,7036,7037,7038,7041],{},"Repare na formulação: o compilador não procura motivos para mandar algo para o heap. Ele tenta ",[294,7039,7040],{},"provar"," que pode manter na stack, e o heap é o que sobra quando a prova falha. É uma diferença importante, e ela explica boa parte do comportamento que parece estranho depois.",[45,7043,7044],{},"O comentário no topo do código-fonte do analisador enuncia as duas regras que sustentam tudo:",[326,7046,7047,7050],{},[45,7048,7049],{},"\"(1) ponteiros para objetos na stack não podem ser guardados no heap e (2) ponteiros para um objeto na stack não podem sobreviver a esse objeto\"",[45,7051,336,7052],{},[49,7053,7056],{"href":7054,"rel":7055},"https:\u002F\u002Fgithub.com\u002Fgolang\u002Fgo\u002Fblob\u002Fmaster\u002Fsrc\u002Fcmd\u002Fcompile\u002Finternal\u002Fescape\u002Fescape.go",[341],[55,7057,7058],{},"cmd\u002Fcompile\u002Finternal\u002Fescape\u002Fescape.go",[45,7060,7061,7062,7065,7066,7068,7069,7071],{},"Voltando ao ",[55,7063,7064],{},"NewCounter",": o ponteiro devolvido sobrevive ao frame, logo a regra (2) seria violada se ",[55,7067,870],{}," ficasse na stack. O compilador então move ",[55,7070,870],{}," para o heap. O programa continua correto sem você fazer nada, e é exatamente esse \"sem você fazer nada\" que o C não oferece.",[45,7073,7074,7075,7078],{},"Uma consequência prática que quase ninguém aproveita: ",[55,7076,7077],{},"new(T)"," pode perfeitamente ficar na stack.",[67,7080,7082],{"className":69,"code":7081,"language":71,"meta":72,"style":72},"package cfg\n\ntype Config struct {\n    Retries int\n    Timeout int\n}\n\n\u002F\u002Fgo:noinline\nfunc validate(c *Config) bool { return c.Retries > 0 }\n\nfunc Load() bool {\n    c := new(Config) \u002F\u002F \"new\" não quer dizer heap\n    c.Retries = 3\n    c.Timeout = 5\n    return validate(c)\n}\n",[55,7083,7084,7091,7095,7105,7112,7119,7123,7127,7131,7168,7172,7185,7203,7216,7230,7242],{"__ignoreMap":72},[76,7085,7086,7088],{"class":78,"line":79},[76,7087,3412],{"class":82},[76,7089,7090],{"class":86}," cfg\n",[76,7092,7093],{"class":78,"line":97},[76,7094,128],{"emptyLinePlaceholder":33},[76,7096,7097,7099,7101,7103],{"class":78,"line":119},[76,7098,83],{"class":82},[76,7100,798],{"class":86},[76,7102,90],{"class":82},[76,7104,94],{"class":93},[76,7106,7107,7110],{"class":78,"line":125},[76,7108,7109],{"class":100},"    Retries ",[76,7111,116],{"class":109},[76,7113,7114,7117],{"class":78,"line":131},[76,7115,7116],{"class":100},"    Timeout ",[76,7118,116],{"class":109},[76,7120,7121],{"class":78,"line":173},[76,7122,122],{"class":93},[76,7124,7125],{"class":78,"line":194},[76,7126,128],{"emptyLinePlaceholder":33},[76,7128,7129],{"class":78,"line":221},[76,7130,3603],{"class":486},[76,7132,7133,7135,7138,7140,7142,7144,7147,7149,7151,7153,7155,7157,7159,7162,7164,7166],{"class":78,"line":581},[76,7134,134],{"class":82},[76,7136,7137],{"class":153}," validate",[76,7139,157],{"class":93},[76,7141,870],{"class":140},[76,7143,726],{"class":82},[76,7145,7146],{"class":86},"Config",[76,7148,150],{"class":93},[76,7150,168],{"class":109},[76,7152,1788],{"class":93},[76,7154,3626],{"class":197},[76,7156,2190],{"class":100},[76,7158,179],{"class":93},[76,7160,7161],{"class":100},"Retries ",[76,7163,4799],{"class":82},[76,7165,1090],{"class":217},[76,7167,2454],{"class":93},[76,7169,7170],{"class":78,"line":586},[76,7171,128],{"emptyLinePlaceholder":33},[76,7173,7174,7176,7179,7181,7183],{"class":78,"line":946},[76,7175,134],{"class":82},[76,7177,7178],{"class":153}," Load",[76,7180,723],{"class":93},[76,7182,168],{"class":109},[76,7184,94],{"class":93},[76,7186,7187,7189,7191,7194,7196,7198,7200],{"class":78,"line":1932},[76,7188,6907],{"class":100},[76,7190,524],{"class":82},[76,7192,7193],{"class":153}," new",[76,7195,157],{"class":93},[76,7197,7146],{"class":86},[76,7199,150],{"class":93},[76,7201,7202],{"class":486}," \u002F\u002F \"new\" não quer dizer heap\n",[76,7204,7205,7207,7209,7211,7213],{"class":78,"line":1937},[76,7206,2170],{"class":100},[76,7208,179],{"class":93},[76,7210,7161],{"class":100},[76,7212,1126],{"class":82},[76,7214,7215],{"class":217}," 3\n",[76,7217,7218,7220,7222,7225,7227],{"class":78,"line":2281},[76,7219,2170],{"class":100},[76,7221,179],{"class":93},[76,7223,7224],{"class":100},"Timeout ",[76,7226,1126],{"class":82},[76,7228,7229],{"class":217}," 5\n",[76,7231,7232,7234,7236,7238,7240],{"class":78,"line":2297},[76,7233,198],{"class":197},[76,7235,7137],{"class":153},[76,7237,157],{"class":93},[76,7239,870],{"class":100},[76,7241,270],{"class":93},[76,7243,7244],{"class":78,"line":2315},[76,7245,122],{"class":93},[67,7247,7250],{"className":7248,"code":7249,"language":276,"meta":72},[274],".\u002Fcfg.go:9:15:  c does not escape\n.\u002Fcfg.go:12:10: new(Config) does not escape\n",[55,7251,7249],{"__ignoreMap":72},[45,7253,7254,7255,7258,7259,7262,7263,7265],{},"O ponteiro entra em ",[55,7256,7257],{},"validate",", é lido e morre ali. Nada sobrevive ao frame de ",[55,7260,7261],{},"Load",", então a prova passa e o ",[55,7264,7000],{}," fica na stack.",[315,7267,7269,7270],{"id":7268},"parte-2-enxergando-a-decisão-com-gcflags-m","Parte 2: enxergando a decisão com ",[55,7271,7272],{},"-gcflags=-m",[45,7274,7275,7276,7279],{},"Você não precisa adivinhar nada disso. A flag ",[55,7277,7278],{},"-m"," pede ao compilador que imprima as decisões de otimização:",[67,7281,7285],{"className":7282,"code":7283,"language":7284,"meta":72,"style":72},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","go build -gcflags=-m .\u002F...\n","bash",[55,7286,7287],{"__ignoreMap":72},[76,7288,7289,7291,7294,7298],{"class":78,"line":79},[76,7290,71],{"class":86},[76,7292,7293],{"class":264}," build",[76,7295,7297],{"class":7296},"stzsN"," -gcflags=-m",[76,7299,7300],{"class":264}," .\u002F...\n",[45,7302,7303],{},"Vamos a um exemplo com três variações do mesmo cálculo:",[67,7305,7307],{"className":69,"code":7306,"language":71,"meta":72,"style":72},"package geo\n\ntype Point struct {\n    X, Y int\n}\n\n\u002F\u002F 1. valor local, sem ponteiro\nfunc DistanceSquared(x, y int) int {\n    p := Point{X: x, Y: y}\n    return p.X*p.X + p.Y*p.Y\n}\n\n\u002F\u002F 2. pega o endereço, mas o ponteiro morre aqui\nfunc DistanceSquaredPtr(x, y int) int {\n    p := &Point{X: x, Y: y}\n    return p.X*p.X + p.Y*p.Y\n}\n\n\u002F\u002F 3. o ponteiro sai da função\nfunc NewPoint(x, y int) *Point {\n    return &Point{X: x, Y: y}\n}\n",[55,7308,7309,7316,7320,7331,7343,7347,7351,7356,7380,7410,7448,7452,7456,7461,7484,7513,7547,7551,7555,7560,7586,7613],{"__ignoreMap":72},[76,7310,7311,7313],{"class":78,"line":79},[76,7312,3412],{"class":82},[76,7314,7315],{"class":86}," geo\n",[76,7317,7318],{"class":78,"line":97},[76,7319,128],{"emptyLinePlaceholder":33},[76,7321,7322,7324,7327,7329],{"class":78,"line":119},[76,7323,83],{"class":82},[76,7325,7326],{"class":86}," Point",[76,7328,90],{"class":82},[76,7330,94],{"class":93},[76,7332,7333,7336,7338,7341],{"class":78,"line":125},[76,7334,7335],{"class":100},"    X",[76,7337,518],{"class":93},[76,7339,7340],{"class":100}," Y ",[76,7342,116],{"class":109},[76,7344,7345],{"class":78,"line":131},[76,7346,122],{"class":93},[76,7348,7349],{"class":78,"line":173},[76,7350,128],{"emptyLinePlaceholder":33},[76,7352,7353],{"class":78,"line":194},[76,7354,7355],{"class":486},"\u002F\u002F 1. valor local, sem ponteiro\n",[76,7357,7358,7360,7363,7365,7367,7369,7372,7374,7376,7378],{"class":78,"line":221},[76,7359,134],{"class":82},[76,7361,7362],{"class":153}," DistanceSquared",[76,7364,157],{"class":93},[76,7366,6869],{"class":140},[76,7368,518],{"class":93},[76,7370,7371],{"class":140}," y",[76,7373,2274],{"class":109},[76,7375,150],{"class":93},[76,7377,2274],{"class":109},[76,7379,94],{"class":93},[76,7381,7382,7385,7387,7389,7391,7394,7396,7399,7401,7404,7406,7408],{"class":78,"line":581},[76,7383,7384],{"class":100},"    p ",[76,7386,524],{"class":82},[76,7388,7326],{"class":86},[76,7390,742],{"class":93},[76,7392,7393],{"class":100},"X",[76,7395,359],{"class":93},[76,7397,7398],{"class":100}," x",[76,7400,518],{"class":93},[76,7402,7403],{"class":100}," Y",[76,7405,359],{"class":93},[76,7407,7371],{"class":100},[76,7409,122],{"class":93},[76,7411,7412,7414,7417,7419,7421,7423,7425,7427,7430,7432,7434,7436,7439,7441,7443,7445],{"class":78,"line":586},[76,7413,198],{"class":197},[76,7415,7416],{"class":100}," p",[76,7418,179],{"class":93},[76,7420,7393],{"class":100},[76,7422,144],{"class":82},[76,7424,45],{"class":100},[76,7426,179],{"class":93},[76,7428,7429],{"class":100},"X ",[76,7431,2412],{"class":82},[76,7433,7416],{"class":100},[76,7435,179],{"class":93},[76,7437,7438],{"class":100},"Y",[76,7440,144],{"class":82},[76,7442,45],{"class":100},[76,7444,179],{"class":93},[76,7446,7447],{"class":100},"Y\n",[76,7449,7450],{"class":78,"line":946},[76,7451,122],{"class":93},[76,7453,7454],{"class":78,"line":1932},[76,7455,128],{"emptyLinePlaceholder":33},[76,7457,7458],{"class":78,"line":1937},[76,7459,7460],{"class":486},"\u002F\u002F 2. pega o endereço, mas o ponteiro morre aqui\n",[76,7462,7463,7465,7468,7470,7472,7474,7476,7478,7480,7482],{"class":78,"line":2281},[76,7464,134],{"class":82},[76,7466,7467],{"class":153}," DistanceSquaredPtr",[76,7469,157],{"class":93},[76,7471,6869],{"class":140},[76,7473,518],{"class":93},[76,7475,7371],{"class":140},[76,7477,2274],{"class":109},[76,7479,150],{"class":93},[76,7481,2274],{"class":109},[76,7483,94],{"class":93},[76,7485,7486,7488,7490,7492,7495,7497,7499,7501,7503,7505,7507,7509,7511],{"class":78,"line":2297},[76,7487,7384],{"class":100},[76,7489,524],{"class":82},[76,7491,737],{"class":82},[76,7493,7494],{"class":86},"Point",[76,7496,742],{"class":93},[76,7498,7393],{"class":100},[76,7500,359],{"class":93},[76,7502,7398],{"class":100},[76,7504,518],{"class":93},[76,7506,7403],{"class":100},[76,7508,359],{"class":93},[76,7510,7371],{"class":100},[76,7512,122],{"class":93},[76,7514,7515,7517,7519,7521,7523,7525,7527,7529,7531,7533,7535,7537,7539,7541,7543,7545],{"class":78,"line":2315},[76,7516,198],{"class":197},[76,7518,7416],{"class":100},[76,7520,179],{"class":93},[76,7522,7393],{"class":100},[76,7524,144],{"class":82},[76,7526,45],{"class":100},[76,7528,179],{"class":93},[76,7530,7429],{"class":100},[76,7532,2412],{"class":82},[76,7534,7416],{"class":100},[76,7536,179],{"class":93},[76,7538,7438],{"class":100},[76,7540,144],{"class":82},[76,7542,45],{"class":100},[76,7544,179],{"class":93},[76,7546,7447],{"class":100},[76,7548,7549],{"class":78,"line":2335},[76,7550,122],{"class":93},[76,7552,7553],{"class":78,"line":4917},[76,7554,128],{"emptyLinePlaceholder":33},[76,7556,7557],{"class":78,"line":4922},[76,7558,7559],{"class":486},"\u002F\u002F 3. o ponteiro sai da função\n",[76,7561,7563,7565,7568,7570,7572,7574,7576,7578,7580,7582,7584],{"class":78,"line":7562},20,[76,7564,134],{"class":82},[76,7566,7567],{"class":153}," NewPoint",[76,7569,157],{"class":93},[76,7571,6869],{"class":140},[76,7573,518],{"class":93},[76,7575,7371],{"class":140},[76,7577,2274],{"class":109},[76,7579,150],{"class":93},[76,7581,726],{"class":82},[76,7583,7494],{"class":86},[76,7585,94],{"class":93},[76,7587,7589,7591,7593,7595,7597,7599,7601,7603,7605,7607,7609,7611],{"class":78,"line":7588},21,[76,7590,198],{"class":197},[76,7592,737],{"class":82},[76,7594,7494],{"class":86},[76,7596,742],{"class":93},[76,7598,7393],{"class":100},[76,7600,359],{"class":93},[76,7602,7398],{"class":100},[76,7604,518],{"class":93},[76,7606,7403],{"class":100},[76,7608,359],{"class":93},[76,7610,7371],{"class":100},[76,7612,122],{"class":93},[76,7614,7615],{"class":78,"line":6806},[76,7616,122],{"class":93},[45,7618,7619],{},"A saída relevante:",[67,7621,7624],{"className":7622,"code":7623,"language":276,"meta":72},[274],".\u002Fgeo.go:15:7: &Point{...} does not escape\n.\u002Fgeo.go:21:9: &Point{...} escapes to heap\n",[55,7625,7623],{"__ignoreMap":72},[45,7627,7628,7629,7635],{},"O caso 1 nem aparece: sem ponteiro, não há o que analisar. O caso 2 mostra a parte contraintuitiva: ",[294,7630,7631,7632,7634],{},"usar ",[55,7633,7008],{}," não manda nada para o heap",". O endereço nasce e morre dentro do frame, então a prova passa. Só o caso 3 escapa, porque o endereço atravessa a fronteira da função.",[45,7637,7638],{},"Vale conhecer as três formas de usar a flag:",[2734,7640,7641,7651],{},[2737,7642,7643],{},[2740,7644,7645,7648],{},[2743,7646,7647],{},"Comando",[2743,7649,7650],{},"Para que serve",[2750,7652,7653,7663,7677],{},[2740,7654,7655,7660],{},[2755,7656,7657],{},[55,7658,7659],{},"go build -gcflags=-m",[2755,7661,7662],{},"decisões do pacote que você listou",[2740,7664,7665,7670],{},[2755,7666,7667],{},[55,7668,7669],{},"go build -gcflags=-m=2",[2755,7671,7672,7673,7676],{},"acrescenta o ",[344,7674,7675],{},"motivo"," de cada escape, seguindo o caminho no grafo",[2740,7678,7679,7684],{},[2755,7680,7681],{},[55,7682,7683],{},"go build -gcflags=all=-m",[2755,7685,7686,7687,7690],{},"inclui as dependências; ",[55,7688,7689],{},"all="," é um padrão de pacotes, não um modificador da flag",[985,7692,7693],{},[45,7694,7695,7696,7699,7700,7703,7704,7707,7708,179],{},"O prefixo ",[55,7697,7698],{},"pattern="," do ",[55,7701,7702],{},"-gcflags"," é documentado em ",[55,7705,7706],{},"go help build",": sem ele, os argumentos valem apenas para os pacotes nomeados na linha de comando. Se você quiser ver o que acontece dentro de uma biblioteca de terceiros, precisa de ",[55,7709,7689],{},[1301,7711,7713],{"id":7712},"lendo-o-vocabulário-da-saída","Lendo o vocabulário da saída",[45,7715,7716,7717,7719],{},"A saída do ",[55,7718,7278],{}," tem um jargão próprio, e ele diz mais do que \"escapou ou não\". Considere um trecho realista de uma camada de repositório:",[67,7721,7723],{"className":69,"code":7722,"language":71,"meta":72,"style":72},"package orders\n\ntype Order struct {\n    ID     int64\n    Total  int64\n    Status string\n}\n\n\u002F\u002F só lê o parâmetro\nfunc isPaid(o *Order) bool {\n    return o.Status == \"paid\"\n}\n\n\u002F\u002F devolve o parâmetro\nfunc FirstPaid(orders []*Order) *Order {\n    for _, o := range orders {\n        if isPaid(o) {\n            return o\n        }\n    }\n    return nil\n}\n\ntype Registry struct {\n    byID map[int64]*Order\n}\n\n\u002F\u002F guarda o parâmetro em uma estrutura de vida longa\nfunc (r *Registry) Add(o *Order) {\n    r.byID[o.ID] = o\n}\n",[55,7724,7725,7732,7736,7746,7754,7761,7768,7772,7776,7781,7802,7820,7824,7828,7833,7858,7876,7890,7898,7902,7906,7913,7917,7922,7934,7953,7958,7963,7969,8000,8024],{"__ignoreMap":72},[76,7726,7727,7729],{"class":78,"line":79},[76,7728,3412],{"class":82},[76,7730,7731],{"class":86}," orders\n",[76,7733,7734],{"class":78,"line":97},[76,7735,128],{"emptyLinePlaceholder":33},[76,7737,7738,7740,7742,7744],{"class":78,"line":119},[76,7739,83],{"class":82},[76,7741,2442],{"class":86},[76,7743,90],{"class":82},[76,7745,94],{"class":93},[76,7747,7748,7751],{"class":78,"line":125},[76,7749,7750],{"class":100},"    ID     ",[76,7752,7753],{"class":109},"int64\n",[76,7755,7756,7759],{"class":78,"line":131},[76,7757,7758],{"class":100},"    Total  ",[76,7760,7753],{"class":109},[76,7762,7763,7766],{"class":78,"line":173},[76,7764,7765],{"class":100},"    Status ",[76,7767,818],{"class":109},[76,7769,7770],{"class":78,"line":194},[76,7771,122],{"class":93},[76,7773,7774],{"class":78,"line":221},[76,7775,128],{"emptyLinePlaceholder":33},[76,7777,7778],{"class":78,"line":581},[76,7779,7780],{"class":486},"\u002F\u002F só lê o parâmetro\n",[76,7782,7783,7785,7788,7790,7792,7794,7796,7798,7800],{"class":78,"line":586},[76,7784,134],{"class":82},[76,7786,7787],{"class":153}," isPaid",[76,7789,157],{"class":93},[76,7791,5847],{"class":140},[76,7793,726],{"class":82},[76,7795,2475],{"class":86},[76,7797,150],{"class":93},[76,7799,168],{"class":109},[76,7801,94],{"class":93},[76,7803,7804,7806,7808,7810,7812,7814,7816,7818],{"class":78,"line":946},[76,7805,198],{"class":197},[76,7807,4343],{"class":100},[76,7809,179],{"class":93},[76,7811,2520],{"class":100},[76,7813,387],{"class":82},[76,7815,598],{"class":260},[76,7817,2527],{"class":264},[76,7819,2530],{"class":260},[76,7821,7822],{"class":78,"line":1932},[76,7823,122],{"class":93},[76,7825,7826],{"class":78,"line":1937},[76,7827,128],{"emptyLinePlaceholder":33},[76,7829,7830],{"class":78,"line":2281},[76,7831,7832],{"class":486},"\u002F\u002F devolve o parâmetro\n",[76,7834,7835,7837,7840,7842,7844,7846,7848,7850,7852,7854,7856],{"class":78,"line":2297},[76,7836,134],{"class":82},[76,7838,7839],{"class":153}," FirstPaid",[76,7841,157],{"class":93},[76,7843,4271],{"class":140},[76,7845,1068],{"class":93},[76,7847,144],{"class":82},[76,7849,2475],{"class":86},[76,7851,150],{"class":93},[76,7853,726],{"class":82},[76,7855,2475],{"class":86},[76,7857,94],{"class":93},[76,7859,7860,7862,7864,7866,7868,7870,7872,7874],{"class":78,"line":2315},[76,7861,1107],{"class":197},[76,7863,1426],{"class":100},[76,7865,518],{"class":93},[76,7867,4317],{"class":100},[76,7869,524],{"class":82},[76,7871,568],{"class":197},[76,7873,4324],{"class":100},[76,7875,573],{"class":93},[76,7877,7878,7880,7882,7884,7886,7888],{"class":78,"line":2335},[76,7879,5712],{"class":197},[76,7881,7787],{"class":153},[76,7883,157],{"class":93},[76,7885,5847],{"class":100},[76,7887,150],{"class":93},[76,7889,94],{"class":93},[76,7891,7892,7895],{"class":78,"line":4917},[76,7893,7894],{"class":197},"            return",[76,7896,7897],{"class":100}," o\n",[76,7899,7900],{"class":78,"line":4922},[76,7901,5754],{"class":93},[76,7903,7904],{"class":78,"line":7562},[76,7905,1146],{"class":93},[76,7907,7908,7910],{"class":78,"line":7588},[76,7909,198],{"class":197},[76,7911,7912],{"class":894}," nil\n",[76,7914,7915],{"class":78,"line":6806},[76,7916,122],{"class":93},[76,7918,7920],{"class":78,"line":7919},23,[76,7921,128],{"emptyLinePlaceholder":33},[76,7923,7925,7927,7930,7932],{"class":78,"line":7924},24,[76,7926,83],{"class":82},[76,7928,7929],{"class":86}," Registry",[76,7931,90],{"class":82},[76,7933,94],{"class":93},[76,7935,7937,7940,7942,7944,7946,7948,7950],{"class":78,"line":7936},25,[76,7938,7939],{"class":100},"    byID ",[76,7941,57],{"class":82},[76,7943,106],{"class":93},[76,7945,4282],{"class":109},[76,7947,113],{"class":93},[76,7949,144],{"class":82},[76,7951,7952],{"class":86},"Order\n",[76,7954,7956],{"class":78,"line":7955},26,[76,7957,122],{"class":93},[76,7959,7961],{"class":78,"line":7960},27,[76,7962,128],{"emptyLinePlaceholder":33},[76,7964,7966],{"class":78,"line":7965},28,[76,7967,7968],{"class":486},"\u002F\u002F guarda o parâmetro em uma estrutura de vida longa\n",[76,7970,7972,7974,7976,7978,7980,7983,7985,7988,7990,7992,7994,7996,7998],{"class":78,"line":7971},29,[76,7973,134],{"class":82},[76,7975,137],{"class":93},[76,7977,141],{"class":140},[76,7979,144],{"class":82},[76,7981,7982],{"class":86},"Registry",[76,7984,150],{"class":93},[76,7986,7987],{"class":153}," Add",[76,7989,157],{"class":93},[76,7991,5847],{"class":140},[76,7993,726],{"class":82},[76,7995,2475],{"class":86},[76,7997,150],{"class":93},[76,7999,94],{"class":93},[76,8001,8003,8005,8007,8010,8012,8014,8016,8018,8020,8022],{"class":78,"line":8002},30,[76,8004,176],{"class":100},[76,8006,179],{"class":93},[76,8008,8009],{"class":100},"byID",[76,8011,106],{"class":93},[76,8013,5847],{"class":100},[76,8015,179],{"class":93},[76,8017,4348],{"class":100},[76,8019,113],{"class":93},[76,8021,664],{"class":82},[76,8023,7897],{"class":100},[76,8025,8027],{"class":78,"line":8026},31,[76,8028,122],{"class":93},[45,8030,8031],{},"E a saída:",[67,8033,8036],{"className":8034,"code":8035,"language":276,"meta":72},[274],".\u002Forders.go:10:13: o does not escape\n.\u002Forders.go:15:16: leaking param: orders to result ~r0 level=1\n.\u002Forders.go:29:7:  r does not escape\n.\u002Forders.go:29:24: leaking param: o\n",[55,8037,8035],{"__ignoreMap":72},[45,8039,8040],{},"Traduzindo cada linha:",[2951,8042,8043,8051,8068,8084],{},[2866,8044,8045,8050],{},[294,8046,8047],{},[55,8048,8049],{},"does not escape",": o compilador provou que o valor não sobrevive à chamada. É a resposta que você quer.",[2866,8052,8053,8058,8059,7699,8061,8063,8064,8067],{},[294,8054,8055],{},[55,8056,8057],{},"leaking param: o",": o parâmetro vaza sem qualificação, ou seja, vai parar no heap (aqui, dentro do ",[55,8060,57],{},[55,8062,7982],{},"). Quem chamar ",[55,8065,8066],{},"Add"," vai ter o argumento alocado no heap.",[2866,8069,8070,8075,8076,8079,8080,8083],{},[294,8071,8072],{},[55,8073,8074],{},"leaking param: orders to result ~r0 level=1",": vazamento ",[344,8077,8078],{},"condicional",". O parâmetro não vai para o heap; ele sai pelo resultado da função. O ",[55,8081,8082],{},"level=1"," é o número de dereferências: o que sai é o conteúdo do slice, não o slice em si. Isso é o que permite ao compilador analisar quem chama sem reanalisar a função inteira.",[2866,8085,8086,8091],{},[294,8087,8088],{},[55,8089,8090],{},"moved to heap: x",": aparece quando uma variável declarada localmente precisou virar alocação no heap.",[45,8093,8094,8095,8098,8099,8102,8103,8106,8107,8110],{},"Essa distinção entre \"vaza para o heap\" e \"vaza para o resultado\" é o coração da parte interprocedural. O compilador resume cada função em ",[294,8096,8097],{},"parameter tags"," e usa esse resumo nos pontos de chamada, em vez de reanalisar tudo. Internamente, ele monta um grafo dirigido em que os vértices são variáveis e as arestas são atribuições, com peso igual ao número de dereferências menos o número de operações de endereço (",[55,8100,8101],{},"p = &q"," pesa -1, ",[55,8104,8105],{},"p = q"," pesa 0, ",[55,8108,8109],{},"p = *q"," pesa 1). Depois procura caminhos que violem as duas regras.",[315,8112,8114],{"id":8113},"parte-3-o-que-realmente-faz-um-valor-escapar","Parte 3: o que realmente faz um valor escapar",[45,8116,8117],{},"Na prática, quase todo escape em código de aplicação cai em um destes cinco padrões.",[1301,8119,8121],{"id":8120},"_1-o-ponteiro-sobrevive-ao-frame","1. O ponteiro sobrevive ao frame",[45,8123,8124,8125,8128,8129,8132],{},"Já vimos: retornar ",[55,8126,8127],{},"&local",", enviar um ponteiro por channel, guardar em uma variável global. É o caso óbvio, e normalmente é o caso ",[344,8130,8131],{},"correto",": se o dado precisa viver mais que a chamada, ele tem que estar no heap.",[45,8134,8135,8136,8139],{},"O que não é óbvio é que ",[294,8137,8138],{},"isso interage com o inlining",". Se o construtor for inlinado no chamador, a fronteira que causava o escape simplesmente deixa de existir:",[67,8141,8143],{"className":69,"code":8142,"language":71,"meta":72,"style":72},"package inl\n\ntype Point struct{ X, Y int }\n\nfunc newPoint(x, y int) *Point { return &Point{X: x, Y: y} }\n\nfunc Norm(x, y int) int {\n    p := newPoint(x, y) \u002F\u002F inlinado aqui\n    return p.X*p.X + p.Y*p.Y\n}\n",[55,8144,8145,8152,8156,8177,8181,8233,8237,8260,8281,8315],{"__ignoreMap":72},[76,8146,8147,8149],{"class":78,"line":79},[76,8148,3412],{"class":82},[76,8150,8151],{"class":86}," inl\n",[76,8153,8154],{"class":78,"line":97},[76,8155,128],{"emptyLinePlaceholder":33},[76,8157,8158,8160,8162,8164,8166,8169,8171,8173,8175],{"class":78,"line":119},[76,8159,83],{"class":82},[76,8161,7326],{"class":86},[76,8163,90],{"class":82},[76,8165,742],{"class":93},[76,8167,8168],{"class":100}," X",[76,8170,518],{"class":93},[76,8172,7340],{"class":100},[76,8174,762],{"class":109},[76,8176,2454],{"class":93},[76,8178,8179],{"class":78,"line":125},[76,8180,128],{"emptyLinePlaceholder":33},[76,8182,8183,8185,8188,8190,8192,8194,8196,8198,8200,8202,8204,8206,8208,8210,8212,8214,8216,8218,8220,8222,8224,8226,8228,8231],{"class":78,"line":131},[76,8184,134],{"class":82},[76,8186,8187],{"class":153}," newPoint",[76,8189,157],{"class":93},[76,8191,6869],{"class":140},[76,8193,518],{"class":93},[76,8195,7371],{"class":140},[76,8197,2274],{"class":109},[76,8199,150],{"class":93},[76,8201,726],{"class":82},[76,8203,7494],{"class":86},[76,8205,1788],{"class":93},[76,8207,3626],{"class":197},[76,8209,737],{"class":82},[76,8211,7494],{"class":86},[76,8213,742],{"class":93},[76,8215,7393],{"class":100},[76,8217,359],{"class":93},[76,8219,7398],{"class":100},[76,8221,518],{"class":93},[76,8223,7403],{"class":100},[76,8225,359],{"class":93},[76,8227,7371],{"class":100},[76,8229,8230],{"class":93},"}",[76,8232,2454],{"class":93},[76,8234,8235],{"class":78,"line":173},[76,8236,128],{"emptyLinePlaceholder":33},[76,8238,8239,8241,8244,8246,8248,8250,8252,8254,8256,8258],{"class":78,"line":194},[76,8240,134],{"class":82},[76,8242,8243],{"class":153}," Norm",[76,8245,157],{"class":93},[76,8247,6869],{"class":140},[76,8249,518],{"class":93},[76,8251,7371],{"class":140},[76,8253,2274],{"class":109},[76,8255,150],{"class":93},[76,8257,2274],{"class":109},[76,8259,94],{"class":93},[76,8261,8262,8264,8266,8268,8270,8272,8274,8276,8278],{"class":78,"line":221},[76,8263,7384],{"class":100},[76,8265,524],{"class":82},[76,8267,8187],{"class":153},[76,8269,157],{"class":93},[76,8271,6869],{"class":100},[76,8273,518],{"class":93},[76,8275,7371],{"class":100},[76,8277,150],{"class":93},[76,8279,8280],{"class":486}," \u002F\u002F inlinado aqui\n",[76,8282,8283,8285,8287,8289,8291,8293,8295,8297,8299,8301,8303,8305,8307,8309,8311,8313],{"class":78,"line":581},[76,8284,198],{"class":197},[76,8286,7416],{"class":100},[76,8288,179],{"class":93},[76,8290,7393],{"class":100},[76,8292,144],{"class":82},[76,8294,45],{"class":100},[76,8296,179],{"class":93},[76,8298,7429],{"class":100},[76,8300,2412],{"class":82},[76,8302,7416],{"class":100},[76,8304,179],{"class":93},[76,8306,7438],{"class":100},[76,8308,144],{"class":82},[76,8310,45],{"class":100},[76,8312,179],{"class":93},[76,8314,7447],{"class":100},[76,8316,8317],{"class":78,"line":586},[76,8318,122],{"class":93},[67,8320,8323],{"className":8321,"code":8322,"language":276,"meta":72},[274],".\u002Finl.go:8:15: inlining call to newPoint\n.\u002Finl.go:5:41: &Point{...} escapes to heap     \u002F\u002F newPoint analisada sozinha\n.\u002Finl.go:8:15: &Point{...} does not escape     \u002F\u002F depois de inlinar em Norm\n",[55,8324,8322],{"__ignoreMap":72},[45,8326,8327,8328,417,8331,8334,8335,8337],{},"A mesma expressão aparece duas vezes com veredictos opostos. Não é inconsistência: o compilador roda a etapa de ",[344,8329,8330],{},"devirtualização e inlining",[294,8332,8333],{},"antes"," da escape analysis, então o que ele analisa é o código já expandido. Medindo com ",[55,8336,6370],{},", a diferença é exatamente uma alocação:",[67,8339,8342],{"className":8340,"code":8341,"language":276,"meta":72},[274],"BenchmarkInlined-8     1000000000    1.065 ns\u002Fop     0 B\u002Fop    0 allocs\u002Fop\nBenchmarkNoInline-8      64799066   18.47  ns\u002Fop    16 B\u002Fop    1 allocs\u002Fop\n",[55,8343,8341],{"__ignoreMap":72},[288,8345,8346],{},[45,8347,8348,8349,8352,8353,8356],{},"Nesse benchmark, a segunda versão só é diferente por causa de um ",[55,8350,8351],{},"\u002F\u002Fgo:noinline",". A moral não é \"use noinline\", é o contrário: ",[294,8354,8355],{},"o escape analysis depende do inlining",", e por isso a resposta para \"isso aloca?\" depende do contexto da chamada, não só do corpo da função.",[1301,8358,8360],{"id":8359},"_2-boxing-em-interface","2. Boxing em interface",[45,8362,8363,8364,8366,8367,8370],{},"Esse é o mais comum em código real e o mais fácil de não perceber, porque ele não tem ",[55,8365,7008],{}," nenhum. Uma interface em Go é um par (tipo, ponteiro para o valor). Guardar um valor concreto em uma interface, ",[55,8368,8369],{},"any"," incluso, significa que esse valor precisa de um endereço, e é aí que a escape analysis entra: se o compilador não conseguir provar que a interface fica contida na função, o valor empacotado vai para o heap.",[45,8372,8373],{},"O exemplo mais frequente é logging estruturado:",[67,8375,8377],{"className":69,"code":8376,"language":71,"meta":72,"style":72},"\u002F\u002F chaves e valores soltos: cada valor vira um \"any\"\nfunc logSlowQuery(query string, elapsed time.Duration) {\n    logger.Warn(\"slow query\", \"query\", query, \"elapsed\", elapsed)\n}\n\n\u002F\u002F atributos tipados: nada precisa ser empacotado em any\nfunc logSlowQueryAttrs(ctx context.Context, query string, elapsed time.Duration) {\n    logger.LogAttrs(ctx, slog.LevelWarn, \"slow query\",\n        slog.String(\"query\", query),\n        slog.Duration(\"elapsed\", elapsed),\n    )\n}\n",[55,8378,8379,8384,8415,8462,8466,8470,8475,8515,8549,8574,8596,8601],{"__ignoreMap":72},[76,8380,8381],{"class":78,"line":79},[76,8382,8383],{"class":486},"\u002F\u002F chaves e valores soltos: cada valor vira um \"any\"\n",[76,8385,8386,8388,8391,8393,8396,8398,8400,8403,8406,8408,8411,8413],{"class":78,"line":97},[76,8387,134],{"class":82},[76,8389,8390],{"class":153}," logSlowQuery",[76,8392,157],{"class":93},[76,8394,8395],{"class":140},"query",[76,8397,163],{"class":109},[76,8399,518],{"class":93},[76,8401,8402],{"class":140}," elapsed",[76,8404,8405],{"class":86}," time",[76,8407,179],{"class":93},[76,8409,8410],{"class":86},"Duration",[76,8412,150],{"class":93},[76,8414,94],{"class":93},[76,8416,8417,8420,8422,8425,8427,8429,8432,8434,8436,8438,8440,8442,8444,8447,8449,8451,8454,8456,8458,8460],{"class":78,"line":119},[76,8418,8419],{"class":100},"    logger",[76,8421,179],{"class":93},[76,8423,8424],{"class":153},"Warn",[76,8426,157],{"class":93},[76,8428,261],{"class":260},[76,8430,8431],{"class":264},"slow query",[76,8433,261],{"class":260},[76,8435,518],{"class":93},[76,8437,598],{"class":260},[76,8439,8395],{"class":264},[76,8441,261],{"class":260},[76,8443,518],{"class":93},[76,8445,8446],{"class":100}," query",[76,8448,518],{"class":93},[76,8450,598],{"class":260},[76,8452,8453],{"class":264},"elapsed",[76,8455,261],{"class":260},[76,8457,518],{"class":93},[76,8459,8402],{"class":100},[76,8461,270],{"class":93},[76,8463,8464],{"class":78,"line":125},[76,8465,122],{"class":93},[76,8467,8468],{"class":78,"line":131},[76,8469,128],{"emptyLinePlaceholder":33},[76,8471,8472],{"class":78,"line":173},[76,8473,8474],{"class":486},"\u002F\u002F atributos tipados: nada precisa ser empacotado em any\n",[76,8476,8477,8479,8482,8484,8487,8490,8492,8495,8497,8499,8501,8503,8505,8507,8509,8511,8513],{"class":78,"line":194},[76,8478,134],{"class":82},[76,8480,8481],{"class":153}," logSlowQueryAttrs",[76,8483,157],{"class":93},[76,8485,8486],{"class":140},"ctx",[76,8488,8489],{"class":86}," context",[76,8491,179],{"class":93},[76,8493,8494],{"class":86},"Context",[76,8496,518],{"class":93},[76,8498,8446],{"class":140},[76,8500,163],{"class":109},[76,8502,518],{"class":93},[76,8504,8402],{"class":140},[76,8506,8405],{"class":86},[76,8508,179],{"class":93},[76,8510,8410],{"class":86},[76,8512,150],{"class":93},[76,8514,94],{"class":93},[76,8516,8517,8519,8521,8524,8526,8528,8530,8533,8535,8538,8540,8542,8544,8546],{"class":78,"line":221},[76,8518,8419],{"class":100},[76,8520,179],{"class":93},[76,8522,8523],{"class":153},"LogAttrs",[76,8525,157],{"class":93},[76,8527,8486],{"class":100},[76,8529,518],{"class":93},[76,8531,8532],{"class":100}," slog",[76,8534,179],{"class":93},[76,8536,8537],{"class":100},"LevelWarn",[76,8539,518],{"class":93},[76,8541,598],{"class":260},[76,8543,8431],{"class":264},[76,8545,261],{"class":260},[76,8547,8548],{"class":93},",\n",[76,8550,8551,8554,8556,8559,8561,8563,8565,8567,8569,8571],{"class":78,"line":581},[76,8552,8553],{"class":100},"        slog",[76,8555,179],{"class":93},[76,8557,8558],{"class":153},"String",[76,8560,157],{"class":93},[76,8562,261],{"class":260},[76,8564,8395],{"class":264},[76,8566,261],{"class":260},[76,8568,518],{"class":93},[76,8570,8446],{"class":100},[76,8572,8573],{"class":93},"),\n",[76,8575,8576,8578,8580,8582,8584,8586,8588,8590,8592,8594],{"class":78,"line":586},[76,8577,8553],{"class":100},[76,8579,179],{"class":93},[76,8581,8410],{"class":153},[76,8583,157],{"class":93},[76,8585,261],{"class":260},[76,8587,8453],{"class":264},[76,8589,261],{"class":260},[76,8591,518],{"class":93},[76,8593,8402],{"class":100},[76,8595,8573],{"class":93},[76,8597,8598],{"class":78,"line":946},[76,8599,8600],{"class":93},"    )\n",[76,8602,8603],{"class":78,"line":1932},[76,8604,122],{"class":93},[45,8606,5470,8607,8609,8610,8613,8614,8617],{},[55,8608,7278],{},", a primeira versão acusa ",[55,8611,8612],{},"elapsed escapes to heap",". E com um handler escrevendo em ",[55,8615,8616],{},"io.Discard",", para isolar o custo de formatação:",[67,8619,8622],{"className":8620,"code":8621,"language":276,"meta":72},[274],"BenchmarkLogAny-8     1000000    1028   ns\u002Fop    24 B\u002Fop    1 allocs\u002Fop\nBenchmarkLogAttrs-8   1314451     911.5 ns\u002Fop     0 B\u002Fop    0 allocs\u002Fop\n",[55,8623,8621],{"__ignoreMap":72},[45,8625,8626,8627,8630,8631,8634,8635,8637,8638,8640,8641,8643,8644,8647,8648,2780,8651,8654,8655,8658,8659,8661,8662,8664],{},"Os 24 bytes se decompõem assim: ",[55,8628,8629],{},"Logger.Warn"," recebe ",[55,8632,8633],{},"...any",", então tanto ",[55,8636,8395],{}," quanto ",[55,8639,8453],{}," precisam ser empacotados. Empacotar a ",[55,8642,110],{}," custa 16 bytes (o cabeçalho da string precisa de um endereço) e empacotar o ",[55,8645,8646],{},"time.Duration"," custa mais 8. Já ",[55,8649,8650],{},"slog.String",[55,8652,8653],{},"slog.Duration"," devolvem um ",[55,8656,8657],{},"slog.Attr",", que guarda esses dados em campos próprios da struct, sem passar por ",[55,8660,8369],{},". Zero alocações. É exatamente para isso que a API ",[55,8663,8523],{}," existe.",[985,8666,8667],{},[45,8668,4367,8669,8671,8672,8675,8676,2008,8679,8682],{},[55,8670,8646],{}," daquele benchmark começa em 1 e vai crescendo. Se você fixá-lo em um valor pequeno, a alocação de 8 bytes some: o runtime mantém um array estático de 256 ",[55,8673,8674],{},"uint64"," e devolve um ponteiro para dentro dele quando o valor cabe ali (",[55,8677,8678],{},"staticuint64s",[55,8680,8681],{},"runtime\u002Fiface.go","). É por isso que microbenchmarks com constantes pequenas às vezes mostram zero alocações e o mesmo código em produção mostra uma.",[45,8684,6030,8685,8688],{},[55,8686,8687],{},"fmt.Sprintf",". Um caso concreto: montar uma chave de cache num caminho quente.",[67,8690,8692],{"className":69,"code":8691,"language":71,"meta":72,"style":72},"func cacheKeySprintf(tenantID, userID int64) string {\n    return fmt.Sprintf(\"user:%d:%d\", tenantID, userID)\n}\n\nfunc cacheKeyAppend(tenantID, userID int64) string {\n    buf := make([]byte, 0, 32)\n    buf = append(buf, \"user:\"...)\n    buf = strconv.AppendInt(buf, tenantID, 10)\n    buf = append(buf, ':')\n    buf = strconv.AppendInt(buf, userID, 10)\n    return string(buf)\n}\n",[55,8693,8694,8720,8758,8762,8766,8789,8813,8839,8868,8892,8918,8930],{"__ignoreMap":72},[76,8695,8696,8698,8701,8703,8706,8708,8711,8714,8716,8718],{"class":78,"line":79},[76,8697,134],{"class":82},[76,8699,8700],{"class":153}," cacheKeySprintf",[76,8702,157],{"class":93},[76,8704,8705],{"class":140},"tenantID",[76,8707,518],{"class":93},[76,8709,8710],{"class":140}," userID",[76,8712,8713],{"class":109}," int64",[76,8715,150],{"class":93},[76,8717,163],{"class":109},[76,8719,94],{"class":93},[76,8721,8722,8724,8727,8729,8732,8734,8736,8739,8741,8743,8745,8747,8749,8752,8754,8756],{"class":78,"line":97},[76,8723,198],{"class":197},[76,8725,8726],{"class":100}," fmt",[76,8728,179],{"class":93},[76,8730,8731],{"class":153},"Sprintf",[76,8733,157],{"class":93},[76,8735,261],{"class":260},[76,8737,8738],{"class":264},"user:",[76,8740,1463],{"class":1456},[76,8742,359],{"class":264},[76,8744,1463],{"class":1456},[76,8746,261],{"class":260},[76,8748,518],{"class":93},[76,8750,8751],{"class":100}," tenantID",[76,8753,518],{"class":93},[76,8755,8710],{"class":100},[76,8757,270],{"class":93},[76,8759,8760],{"class":78,"line":119},[76,8761,122],{"class":93},[76,8763,8764],{"class":78,"line":125},[76,8765,128],{"emptyLinePlaceholder":33},[76,8767,8768,8770,8773,8775,8777,8779,8781,8783,8785,8787],{"class":78,"line":131},[76,8769,134],{"class":82},[76,8771,8772],{"class":153}," cacheKeyAppend",[76,8774,157],{"class":93},[76,8776,8705],{"class":140},[76,8778,518],{"class":93},[76,8780,8710],{"class":140},[76,8782,8713],{"class":109},[76,8784,150],{"class":93},[76,8786,163],{"class":109},[76,8788,94],{"class":93},[76,8790,8791,8794,8796,8798,8800,8802,8804,8806,8808,8811],{"class":78,"line":173},[76,8792,8793],{"class":100},"    buf ",[76,8795,524],{"class":82},[76,8797,749],{"class":153},[76,8799,849],{"class":93},[76,8801,852],{"class":109},[76,8803,518],{"class":93},[76,8805,1090],{"class":217},[76,8807,518],{"class":93},[76,8809,8810],{"class":217}," 32",[76,8812,270],{"class":93},[76,8814,8815,8817,8819,8821,8823,8826,8828,8830,8832,8834,8837],{"class":78,"line":194},[76,8816,8793],{"class":100},[76,8818,1126],{"class":82},[76,8820,1129],{"class":153},[76,8822,157],{"class":93},[76,8824,8825],{"class":100},"buf",[76,8827,518],{"class":93},[76,8829,598],{"class":260},[76,8831,8738],{"class":264},[76,8833,261],{"class":260},[76,8835,8836],{"class":82},"...",[76,8838,270],{"class":93},[76,8840,8841,8843,8845,8848,8850,8853,8855,8857,8859,8861,8863,8866],{"class":78,"line":221},[76,8842,8793],{"class":100},[76,8844,1126],{"class":82},[76,8846,8847],{"class":100}," strconv",[76,8849,179],{"class":93},[76,8851,8852],{"class":153},"AppendInt",[76,8854,157],{"class":93},[76,8856,8825],{"class":100},[76,8858,518],{"class":93},[76,8860,8751],{"class":100},[76,8862,518],{"class":93},[76,8864,8865],{"class":217}," 10",[76,8867,270],{"class":93},[76,8869,8870,8872,8874,8876,8878,8880,8882,8885,8887,8890],{"class":78,"line":581},[76,8871,8793],{"class":100},[76,8873,1126],{"class":82},[76,8875,1129],{"class":153},[76,8877,157],{"class":93},[76,8879,8825],{"class":100},[76,8881,518],{"class":93},[76,8883,8884],{"class":260}," '",[76,8886,359],{"class":7296},[76,8888,8889],{"class":260},"'",[76,8891,270],{"class":93},[76,8893,8894,8896,8898,8900,8902,8904,8906,8908,8910,8912,8914,8916],{"class":78,"line":586},[76,8895,8793],{"class":100},[76,8897,1126],{"class":82},[76,8899,8847],{"class":100},[76,8901,179],{"class":93},[76,8903,8852],{"class":153},[76,8905,157],{"class":93},[76,8907,8825],{"class":100},[76,8909,518],{"class":93},[76,8911,8710],{"class":100},[76,8913,518],{"class":93},[76,8915,8865],{"class":217},[76,8917,270],{"class":93},[76,8919,8920,8922,8924,8926,8928],{"class":78,"line":946},[76,8921,198],{"class":197},[76,8923,163],{"class":109},[76,8925,157],{"class":93},[76,8927,8825],{"class":100},[76,8929,270],{"class":93},[76,8931,8932],{"class":78,"line":1932},[76,8933,122],{"class":93},[67,8935,8938],{"className":8936,"code":8937,"language":276,"meta":72},[274],"BenchmarkSprintf-8   10583786   124.7 ns\u002Fop    24 B\u002Fop    2 allocs\u002Fop\nBenchmarkAppend-8    35509032    47.95 ns\u002Fop   16 B\u002Fop    1 allocs\u002Fop\n",[55,8939,8937],{"__ignoreMap":72},[45,8941,4367,8942,8944,8945,2780,8948,8951,8952,8954,8955,8958,8959,8961,8962,5326,8965,8967],{},[55,8943,7278],{}," acusa ",[55,8946,8947],{},"tenantID escapes to heap",[55,8949,8950],{},"userID escapes to heap"," na primeira versão, por causa do ",[55,8953,8633],{},". Um profile de memória mostra de onde vêm os 24 bytes: 16 no ",[55,8956,8957],{},"string(p.buf)"," interno do ",[55,8960,8687],{}," e 8 no empacotamento do ",[55,8963,8964],{},"userID",[55,8966,8705],{}," é a constante 42 e não custa nada, pelo mesmo motivo da observação acima.",[45,8969,8970,8971,8973,8974,2780,8977,8980,8981,8984],{},"Na segunda versão, o ",[55,8972,7278],{}," diz ",[55,8975,8976],{},"make([]byte, 0, 32) does not escape",[55,8978,8979],{},"append does not escape",". O buffer inteiro vive na stack, e só o ",[55,8982,8983],{},"string(buf)"," final aloca, porque strings em Go são imutáveis e o resultado precisa sobreviver à função.",[985,8986,8987],{},[45,8988,8989],{},"Repare que a alocação da string devolvida sobrevive nas duas versões, e não tem como ser diferente: alguém precisa ser dono daqueles bytes depois que a função retorna. Escape analysis não elimina alocações necessárias, ela elimina as acidentais.",[1301,8991,8993],{"id":8992},"_3-guardar-um-ponteiro-em-algo-que-já-está-no-heap","3. Guardar um ponteiro em algo que já está no heap",[45,8995,8996],{},"Esta é a regra (1) em ação, e ela é transitiva. Se você escreve um ponteiro dentro de um slice, map, struct ou closure que está no heap, o alvo tem que ir junto. Não existe ponteiro do heap para a stack.",[45,8998,8999,9000,9003,9004,9006,9007,9009],{},"É por isso que o ",[55,9001,9002],{},"Registry.Add"," do exemplo anterior marca ",[55,9005,8057],{}," sem qualificação: o ",[55,9008,57],{}," sobrevive à chamada, então tudo que entra nele sobrevive também.",[45,9011,9012,9013,9016],{},"Uma consequência que vale internalizar: ",[294,9014,9015],{},"a decisão se propaga para trás",". Uma única linha que guarda um ponteiro em uma estrutura de vida longa faz o valor original escapar, mesmo que ele tenha sido criado dez frames acima.",[1301,9018,9020],{"id":9019},"_4-closures-e-goroutines","4. Closures e goroutines",[45,9022,9023],{},"Uma closure captura variáveis. Se a closure escapa, as variáveis capturadas por referência escapam junto.",[67,9025,9027],{"className":69,"code":9026,"language":71,"meta":72,"style":72},"package jobs\n\nimport \"sync\"\n\ntype Job struct {\n    ID      int\n    Payload string\n}\n\nfunc ProcessAsync(jobs []Job) int {\n    var mu sync.Mutex\n    var wg sync.WaitGroup\n    total := 0\n    for _, j := range jobs {\n        wg.Add(1)\n        go func() {\n            defer wg.Done()\n            mu.Lock()\n            total += len(j.Payload)\n            mu.Unlock()\n        }()\n    }\n    wg.Wait()\n    return total\n}\n",[55,9028,9029,9036,9040,9050,9054,9065,9072,9079,9083,9087,9110,9124,9138,9148,9168,9183,9194,9209,9220,9241,9251,9256,9260,9272,9279],{"__ignoreMap":72},[76,9030,9031,9033],{"class":78,"line":79},[76,9032,3412],{"class":82},[76,9034,9035],{"class":86}," jobs\n",[76,9037,9038],{"class":78,"line":97},[76,9039,128],{"emptyLinePlaceholder":33},[76,9041,9042,9044,9046,9048],{"class":78,"line":119},[76,9043,3424],{"class":197},[76,9045,598],{"class":260},[76,9047,2060],{"class":86},[76,9049,2530],{"class":260},[76,9051,9052],{"class":78,"line":125},[76,9053,128],{"emptyLinePlaceholder":33},[76,9055,9056,9058,9061,9063],{"class":78,"line":131},[76,9057,83],{"class":82},[76,9059,9060],{"class":86}," Job",[76,9062,90],{"class":82},[76,9064,94],{"class":93},[76,9066,9067,9070],{"class":78,"line":173},[76,9068,9069],{"class":100},"    ID      ",[76,9071,116],{"class":109},[76,9073,9074,9077],{"class":78,"line":194},[76,9075,9076],{"class":100},"    Payload ",[76,9078,818],{"class":109},[76,9080,9081],{"class":78,"line":221},[76,9082,122],{"class":93},[76,9084,9085],{"class":78,"line":581},[76,9086,128],{"emptyLinePlaceholder":33},[76,9088,9089,9091,9094,9096,9099,9101,9104,9106,9108],{"class":78,"line":586},[76,9090,134],{"class":82},[76,9092,9093],{"class":153}," ProcessAsync",[76,9095,157],{"class":93},[76,9097,9098],{"class":140},"jobs",[76,9100,1068],{"class":93},[76,9102,9103],{"class":86},"Job",[76,9105,150],{"class":93},[76,9107,2274],{"class":109},[76,9109,94],{"class":93},[76,9111,9112,9114,9117,9119,9121],{"class":78,"line":946},[76,9113,3469],{"class":82},[76,9115,9116],{"class":100}," mu ",[76,9118,2060],{"class":86},[76,9120,179],{"class":93},[76,9122,9123],{"class":86},"Mutex\n",[76,9125,9126,9128,9131,9133,9135],{"class":78,"line":1932},[76,9127,3469],{"class":82},[76,9129,9130],{"class":100}," wg ",[76,9132,2060],{"class":86},[76,9134,179],{"class":93},[76,9136,9137],{"class":86},"WaitGroup\n",[76,9139,9140,9143,9145],{"class":78,"line":1937},[76,9141,9142],{"class":100},"    total ",[76,9144,524],{"class":82},[76,9146,9147],{"class":217}," 0\n",[76,9149,9150,9152,9154,9156,9159,9161,9163,9166],{"class":78,"line":2281},[76,9151,1107],{"class":197},[76,9153,1426],{"class":100},[76,9155,518],{"class":93},[76,9157,9158],{"class":100}," j ",[76,9160,524],{"class":82},[76,9162,568],{"class":197},[76,9164,9165],{"class":100}," jobs ",[76,9167,573],{"class":93},[76,9169,9170,9173,9175,9177,9179,9181],{"class":78,"line":2297},[76,9171,9172],{"class":100},"        wg",[76,9174,179],{"class":93},[76,9176,8066],{"class":153},[76,9178,157],{"class":93},[76,9180,3723],{"class":217},[76,9182,270],{"class":93},[76,9184,9185,9188,9190,9192],{"class":78,"line":2315},[76,9186,9187],{"class":197},"        go",[76,9189,1783],{"class":82},[76,9191,723],{"class":93},[76,9193,94],{"class":93},[76,9195,9196,9199,9202,9204,9207],{"class":78,"line":2335},[76,9197,9198],{"class":197},"            defer",[76,9200,9201],{"class":100}," wg",[76,9203,179],{"class":93},[76,9205,9206],{"class":153},"Done",[76,9208,1270],{"class":93},[76,9210,9211,9214,9216,9218],{"class":78,"line":4917},[76,9212,9213],{"class":100},"            mu",[76,9215,179],{"class":93},[76,9217,2292],{"class":153},[76,9219,1270],{"class":93},[76,9221,9222,9225,9227,9229,9231,9234,9236,9239],{"class":78,"line":4922},[76,9223,9224],{"class":100},"            total ",[76,9226,4887],{"class":82},[76,9228,1095],{"class":153},[76,9230,157],{"class":93},[76,9232,9233],{"class":100},"j",[76,9235,179],{"class":93},[76,9237,9238],{"class":100},"Payload",[76,9240,270],{"class":93},[76,9242,9243,9245,9247,9249],{"class":78,"line":7562},[76,9244,9213],{"class":100},[76,9246,179],{"class":93},[76,9248,2310],{"class":153},[76,9250,1270],{"class":93},[76,9252,9253],{"class":78,"line":7588},[76,9254,9255],{"class":93},"        }()\n",[76,9257,9258],{"class":78,"line":6806},[76,9259,1146],{"class":93},[76,9261,9262,9265,9267,9270],{"class":78,"line":7919},[76,9263,9264],{"class":100},"    wg",[76,9266,179],{"class":93},[76,9268,9269],{"class":153},"Wait",[76,9271,1270],{"class":93},[76,9273,9274,9276],{"class":78,"line":7924},[76,9275,198],{"class":197},[76,9277,9278],{"class":100}," total\n",[76,9280,9281],{"class":78,"line":7936},[76,9282,122],{"class":93},[67,9284,9287],{"className":9285,"code":9286,"language":276,"meta":72},[274],".\u002Fjobs.go:10:19: leaking param content: jobs\n.\u002Fjobs.go:11:6:  moved to heap: mu\n.\u002Fjobs.go:12:6:  moved to heap: wg\n.\u002Fjobs.go:13:2:  moved to heap: total\n.\u002Fjobs.go:16:6:  func literal escapes to heap\n",[55,9288,9286],{"__ignoreMap":72},[45,9290,9291,9292,619,9294,2780,9297,9300],{},"Toda goroutine tem sua própria stack, então uma variável compartilhada entre a goroutine pai e a filha não pode viver na stack de nenhuma das duas. ",[55,9293,2175],{},[55,9295,9296],{},"wg",[55,9298,9299],{},"total"," vão para o heap porque a closure as modifica e precisa do endereço.",[45,9302,9303,9304,9306,9307,9309,9310,9312],{},"Repare no que ",[294,9305,4697],{}," está na lista: ",[55,9308,9233],{},". Como ",[55,9311,9233],{}," só é lido, o compilador a captura por cópia, guardando o valor direto dentro do objeto da closure em vez de criar uma variável separada no heap. É um detalhe pequeno, mas mostra que \"capturou\" e \"escapou\" não são a mesma coisa.",[288,9314,9315],{},[45,9316,9317,9318,2701,9321,9324,9325,9328,9329,179],{},"A mesma função com a closure chamada de forma síncrona (",[55,9319,9320],{},"count(j)",[55,9322,9323],{},"go func(){}()",") não move nada para o heap: o compilador imprime apenas ",[55,9326,9327],{},"jobs does not escape",". O que causa o escape não é a closure, é o ",[55,9330,71],{},[1301,9332,9334],{"id":9333},"_5-tamanho-grande-demais-para-a-stack","5. Tamanho: grande demais para a stack",[45,9336,9337,9338,9341,9342,179],{},"Independentemente do tempo de vida, existe um teto. Se o valor for maior que o limite, ele vai para o heap, e o ",[55,9339,9340],{},"-m=2"," diz o motivo com todas as letras: ",[55,9343,9344],{},"too large for stack",[45,9346,9347],{},"No Go 1.26 os limites são:",[2734,9349,9350,9360],{},[2737,9351,9352],{},[2740,9353,9354,9357],{},[2743,9355,9356],{},"O que",[2743,9358,9359],{},"Limite",[2750,9361,9362,9376],{},[2740,9363,9364,9373],{},[2755,9365,9366,9367,619,9370,150],{},"Declarações explícitas (",[55,9368,9369],{},"var x T",[55,9371,9372],{},"x := ...",[2755,9374,9375],{},"128 KiB",[2740,9377,9378,9388],{},[2755,9379,9380,9381,619,9383,619,9385,9387],{},"Alocações implícitas (",[55,9382,7077],{},[55,9384,7004],{},[55,9386,6775],{}," constante)",[2755,9389,9390],{},"64 KiB",[67,9392,9394],{"className":69,"code":9393,"language":71,"meta":72,"style":72},"\u002F\u002Fgo:noinline\nfunc checksum(b []byte) int {\n    t := 0\n    for _, v := range b {\n        t += int(v)\n    }\n    return t\n}\n\nfunc small() int {\n    var buf [64 * 1024]byte \u002F\u002F 64 KiB: fica na stack\n    return checksum(buf[:])\n}\n\nfunc big() int {\n    var buf [129 * 1024]byte \u002F\u002F 129 KiB: moved to heap\n    return checksum(buf[:])\n}\n",[55,9395,9396,9400,9421,9430,9450,9465,9469,9476,9480,9484,9497,9521,9534,9538,9542,9555,9577,9589],{"__ignoreMap":72},[76,9397,9398],{"class":78,"line":79},[76,9399,3603],{"class":486},[76,9401,9402,9404,9407,9409,9411,9413,9415,9417,9419],{"class":78,"line":97},[76,9403,134],{"class":82},[76,9405,9406],{"class":153}," checksum",[76,9408,157],{"class":93},[76,9410,1596],{"class":140},[76,9412,1068],{"class":93},[76,9414,852],{"class":109},[76,9416,150],{"class":93},[76,9418,2274],{"class":109},[76,9420,94],{"class":93},[76,9422,9423,9426,9428],{"class":78,"line":119},[76,9424,9425],{"class":100},"    t ",[76,9427,524],{"class":82},[76,9429,9147],{"class":217},[76,9431,9432,9434,9436,9438,9441,9443,9445,9448],{"class":78,"line":125},[76,9433,1107],{"class":197},[76,9435,1426],{"class":100},[76,9437,518],{"class":93},[76,9439,9440],{"class":100}," v ",[76,9442,524],{"class":82},[76,9444,568],{"class":197},[76,9446,9447],{"class":100}," b ",[76,9449,573],{"class":93},[76,9451,9452,9455,9457,9459,9461,9463],{"class":78,"line":131},[76,9453,9454],{"class":100},"        t ",[76,9456,4887],{"class":82},[76,9458,2274],{"class":109},[76,9460,157],{"class":93},[76,9462,515],{"class":100},[76,9464,270],{"class":93},[76,9466,9467],{"class":78,"line":173},[76,9468,1146],{"class":93},[76,9470,9471,9473],{"class":78,"line":194},[76,9472,198],{"class":197},[76,9474,9475],{"class":100}," t\n",[76,9477,9478],{"class":78,"line":221},[76,9479,122],{"class":93},[76,9481,9482],{"class":78,"line":581},[76,9483,128],{"emptyLinePlaceholder":33},[76,9485,9486,9488,9491,9493,9495],{"class":78,"line":586},[76,9487,134],{"class":82},[76,9489,9490],{"class":153}," small",[76,9492,723],{"class":93},[76,9494,2274],{"class":109},[76,9496,94],{"class":93},[76,9498,9499,9501,9504,9506,9509,9511,9514,9516,9518],{"class":78,"line":946},[76,9500,3469],{"class":82},[76,9502,9503],{"class":100}," buf ",[76,9505,106],{"class":93},[76,9507,9508],{"class":217},"64",[76,9510,726],{"class":82},[76,9512,9513],{"class":217}," 1024",[76,9515,113],{"class":100},[76,9517,852],{"class":109},[76,9519,9520],{"class":486}," \u002F\u002F 64 KiB: fica na stack\n",[76,9522,9523,9525,9527,9529,9531],{"class":78,"line":1932},[76,9524,198],{"class":197},[76,9526,9406],{"class":153},[76,9528,157],{"class":93},[76,9530,8825],{"class":100},[76,9532,9533],{"class":93},"[:])\n",[76,9535,9536],{"class":78,"line":1937},[76,9537,122],{"class":93},[76,9539,9540],{"class":78,"line":2281},[76,9541,128],{"emptyLinePlaceholder":33},[76,9543,9544,9546,9549,9551,9553],{"class":78,"line":2297},[76,9545,134],{"class":82},[76,9547,9548],{"class":153}," big",[76,9550,723],{"class":93},[76,9552,2274],{"class":109},[76,9554,94],{"class":93},[76,9556,9557,9559,9561,9563,9566,9568,9570,9572,9574],{"class":78,"line":2315},[76,9558,3469],{"class":82},[76,9560,9503],{"class":100},[76,9562,106],{"class":93},[76,9564,9565],{"class":217},"129",[76,9567,726],{"class":82},[76,9569,9513],{"class":217},[76,9571,113],{"class":100},[76,9573,852],{"class":109},[76,9575,9576],{"class":486}," \u002F\u002F 129 KiB: moved to heap\n",[76,9578,9579,9581,9583,9585,9587],{"class":78,"line":2335},[76,9580,198],{"class":197},[76,9582,9406],{"class":153},[76,9584,157],{"class":93},[76,9586,8825],{"class":100},[76,9588,9533],{"class":93},[76,9590,9591],{"class":78,"line":4917},[76,9592,122],{"class":93},[45,9594,9595,9596,3310,9598,417,9601,9604],{},"Existe ainda um caso à parte: ",[55,9597,6775],{},[55,9599,9600],{},"n",[294,9602,9603],{},"não constante",". Aqui o compilador não sabe o tamanho em tempo de compilação, então, desde o Go 1.25, ele gera as duas versões e escolhe em tempo de execução, reservando uma pequena área fixa na stack (32 bytes por padrão) e caindo para o heap quando não couber:",[67,9606,9609],{"className":9607,"code":9608,"language":276,"meta":72},[274],"BenchmarkVarMakeSmall-8   160536319    7.452 ns\u002Fop     0 B\u002Fop    0 allocs\u002Fop   \u002F\u002F n = 16\nBenchmarkVarMakeLarge-8    19328575   63.39  ns\u002Fop    64 B\u002Fop    1 allocs\u002Fop   \u002F\u002F n = 64\n",[55,9610,9608],{"__ignoreMap":72},[45,9612,9613,9614,619,9617,9620,9621,9623],{},"Os dois benchmarks rodam a ",[294,9615,9616],{},"mesma linha de código",[55,9618,9619],{},"make([]byte, n)",", com valores diferentes de ",[55,9622,9600],{},". É provavelmente o exemplo mais literal de que \"essa linha aloca?\" não é uma pergunta bem formada em Go.",[315,9625,9627],{"id":9626},"parte-4-medindo-não-adivinhando","Parte 4: medindo, não adivinhando",[45,9629,9630,9631,9633],{},"Chegando aqui, a tentação é sair rodando ",[55,9632,7272],{}," no projeto inteiro. Não faça isso: um serviço médio produz milhares de linhas de saída, a maioria irrelevante. Escape analysis é uma ferramenta de diagnóstico, não de auditoria.",[45,9635,9636],{},"O fluxo que funciona é o inverso, e começa pela alocação, não pelo escape:",[45,9638,9639,417,9642,9644,9645,9647],{},[294,9640,9641],{},"1. Meça alocações onde importa.",[55,9643,6370],{}," mostra ",[55,9646,6366],{},", que é o número que se traduz direto em pressão de GC:",[67,9649,9651],{"className":7282,"code":9650,"language":7284,"meta":72,"style":72},"go test -run='^$' -bench=. -benchmem .\u002F...\n",[55,9652,9653],{"__ignoreMap":72},[76,9654,9655,9657,9660,9663,9665,9668,9670,9673,9676],{"class":78,"line":79},[76,9656,71],{"class":86},[76,9658,9659],{"class":264}," test",[76,9661,9662],{"class":7296}," -run=",[76,9664,8889],{"class":260},[76,9666,9667],{"class":264},"^$",[76,9669,8889],{"class":260},[76,9671,9672],{"class":7296}," -bench=.",[76,9674,9675],{"class":7296}," -benchmem",[76,9677,7300],{"class":264},[45,9679,9680,9683],{},[294,9681,9682],{},"2. Descubra quem aloca."," Um profile de memória aponta as linhas responsáveis:",[67,9685,9687],{"className":7282,"code":9686,"language":7284,"meta":72,"style":72},"go test -run='^$' -bench=BenchmarkSprintf -memprofile=mem.out .\u002Fkey\u002F\ngo tool pprof -top -sample_index=alloc_objects key.test mem.out\n",[55,9688,9689,9712],{"__ignoreMap":72},[76,9690,9691,9693,9695,9697,9699,9701,9703,9706,9709],{"class":78,"line":79},[76,9692,71],{"class":86},[76,9694,9659],{"class":264},[76,9696,9662],{"class":7296},[76,9698,8889],{"class":260},[76,9700,9667],{"class":264},[76,9702,8889],{"class":260},[76,9704,9705],{"class":7296}," -bench=BenchmarkSprintf",[76,9707,9708],{"class":7296}," -memprofile=mem.out",[76,9710,9711],{"class":264}," .\u002Fkey\u002F\n",[76,9713,9714,9716,9719,9722,9725,9728,9731],{"class":78,"line":97},[76,9715,71],{"class":86},[76,9717,9718],{"class":264}," tool",[76,9720,9721],{"class":264}," pprof",[76,9723,9724],{"class":7296}," -top",[76,9726,9727],{"class":7296}," -sample_index=alloc_objects",[76,9729,9730],{"class":264}," key.test",[76,9732,9733],{"class":264}," mem.out\n",[67,9735,9738],{"className":9736,"code":9737,"language":276,"meta":72},[274],"      flat  flat%   sum%        cum   cum%\n  10780836 67.00% 67.00%   10780836 67.00%  fmt.Sprintf\n   5308497 32.99%   100%   16089333   100%  key.cacheKeySprintf (inline)\n",[55,9739,9737],{"__ignoreMap":72},[45,9741,9742,9743,9746,9747,9750,9751,9754],{},"Use ",[55,9744,9745],{},"alloc_objects"," (quantidade) e ",[55,9748,9749],{},"alloc_space"," (bytes) para investigar pressão de GC. O default do pprof é ",[55,9752,9753],{},"inuse_space",", que responde outra pergunta: o que ainda está vivo agora.",[45,9756,9757,9763,9764,9767],{},[294,9758,9759,9760,9762],{},"3. Só então use ",[55,9761,7272],{},", no pacote que o profile apontou",", para entender ",[344,9765,9766],{},"por que"," aquela linha específica aloca.",[45,9769,9770],{},"Nessa ordem, você gasta esforço só onde tem retorno. Na ordem inversa, você reescreve código legível para eliminar alocações que aconteciam três vezes por requisição.",[315,9772,2422],{"id":2421},[45,9774,9775],{},"Esta é a parte que separa entender o mecanismo de usá-lo bem.",[45,9777,9778,417,9781,9783,9784,9787],{},[294,9779,9780],{},"A análise é conservadora, e isso é assimétrico.",[55,9782,8049],{}," é uma prova: pode confiar. ",[55,9785,9786],{},"escapes to heap"," é a ausência de prova, e pode perfeitamente ser um falso positivo. O caso mais comum é a chamada indireta:",[67,9789,9791],{"className":69,"code":9790,"language":71,"meta":72,"style":72},"package metrics\n\ntype Metric struct {\n    Name  string\n    Value float64\n}\n\nfunc report(m *Metric) { _ = m.Value }\n\nfunc DirectCall() {\n    m := &Metric{Name: \"latency\", Value: 1.5}\n    report(m) \u002F\u002F chamada estática\n}\n\nfunc IndirectCall(fn func(*Metric)) {\n    m := &Metric{Name: \"latency\", Value: 1.5}\n    fn(m) \u002F\u002F chamada indireta\n}\n",[55,9792,9793,9800,9804,9815,9822,9830,9834,9838,9871,9875,9886,9923,9937,9941,9945,9969,10001,10015],{"__ignoreMap":72},[76,9794,9795,9797],{"class":78,"line":79},[76,9796,3412],{"class":82},[76,9798,9799],{"class":86}," metrics\n",[76,9801,9802],{"class":78,"line":97},[76,9803,128],{"emptyLinePlaceholder":33},[76,9805,9806,9808,9811,9813],{"class":78,"line":119},[76,9807,83],{"class":82},[76,9809,9810],{"class":86}," Metric",[76,9812,90],{"class":82},[76,9814,94],{"class":93},[76,9816,9817,9820],{"class":78,"line":125},[76,9818,9819],{"class":100},"    Name  ",[76,9821,818],{"class":109},[76,9823,9824,9827],{"class":78,"line":131},[76,9825,9826],{"class":100},"    Value ",[76,9828,9829],{"class":109},"float64\n",[76,9831,9832],{"class":78,"line":173},[76,9833,122],{"class":93},[76,9835,9836],{"class":78,"line":194},[76,9837,128],{"emptyLinePlaceholder":33},[76,9839,9840,9842,9845,9847,9849,9851,9854,9856,9858,9860,9862,9864,9866,9869],{"class":78,"line":221},[76,9841,134],{"class":82},[76,9843,9844],{"class":153}," report",[76,9846,157],{"class":93},[76,9848,471],{"class":140},[76,9850,726],{"class":82},[76,9852,9853],{"class":86},"Metric",[76,9855,150],{"class":93},[76,9857,1788],{"class":93},[76,9859,1601],{"class":100},[76,9861,1126],{"class":82},[76,9863,527],{"class":100},[76,9865,179],{"class":93},[76,9867,9868],{"class":100},"Value ",[76,9870,122],{"class":93},[76,9872,9873],{"class":78,"line":581},[76,9874,128],{"emptyLinePlaceholder":33},[76,9876,9877,9879,9882,9884],{"class":78,"line":586},[76,9878,134],{"class":82},[76,9880,9881],{"class":153}," DirectCall",[76,9883,723],{"class":93},[76,9885,94],{"class":93},[76,9887,9888,9891,9893,9895,9897,9899,9902,9904,9906,9909,9911,9913,9916,9918,9921],{"class":78,"line":946},[76,9889,9890],{"class":100},"    m ",[76,9892,524],{"class":82},[76,9894,737],{"class":82},[76,9896,9853],{"class":86},[76,9898,742],{"class":93},[76,9900,9901],{"class":100},"Name",[76,9903,359],{"class":93},[76,9905,598],{"class":260},[76,9907,9908],{"class":264},"latency",[76,9910,261],{"class":260},[76,9912,518],{"class":93},[76,9914,9915],{"class":100}," Value",[76,9917,359],{"class":93},[76,9919,9920],{"class":217}," 1.5",[76,9922,122],{"class":93},[76,9924,9925,9928,9930,9932,9934],{"class":78,"line":1932},[76,9926,9927],{"class":153},"    report",[76,9929,157],{"class":93},[76,9931,471],{"class":100},[76,9933,150],{"class":93},[76,9935,9936],{"class":486}," \u002F\u002F chamada estática\n",[76,9938,9939],{"class":78,"line":1937},[76,9940,122],{"class":93},[76,9942,9943],{"class":78,"line":2281},[76,9944,128],{"emptyLinePlaceholder":33},[76,9946,9947,9949,9952,9954,9957,9959,9961,9963,9965,9967],{"class":78,"line":2297},[76,9948,134],{"class":82},[76,9950,9951],{"class":153}," IndirectCall",[76,9953,157],{"class":93},[76,9955,9956],{"class":140},"fn",[76,9958,1783],{"class":82},[76,9960,157],{"class":93},[76,9962,144],{"class":82},[76,9964,9853],{"class":86},[76,9966,507],{"class":93},[76,9968,94],{"class":93},[76,9970,9971,9973,9975,9977,9979,9981,9983,9985,9987,9989,9991,9993,9995,9997,9999],{"class":78,"line":2315},[76,9972,9890],{"class":100},[76,9974,524],{"class":82},[76,9976,737],{"class":82},[76,9978,9853],{"class":86},[76,9980,742],{"class":93},[76,9982,9901],{"class":100},[76,9984,359],{"class":93},[76,9986,598],{"class":260},[76,9988,9908],{"class":264},[76,9990,261],{"class":260},[76,9992,518],{"class":93},[76,9994,9915],{"class":100},[76,9996,359],{"class":93},[76,9998,9920],{"class":217},[76,10000,122],{"class":93},[76,10002,10003,10006,10008,10010,10012],{"class":78,"line":2335},[76,10004,10005],{"class":153},"    fn",[76,10007,157],{"class":93},[76,10009,471],{"class":100},[76,10011,150],{"class":93},[76,10013,10014],{"class":486}," \u002F\u002F chamada indireta\n",[76,10016,10017],{"class":78,"line":4917},[76,10018,122],{"class":93},[67,10020,10023],{"className":10021,"code":10022,"language":276,"meta":72},[274],".\u002Fmetrics.go:11:7: &Metric{...} does not escape\n.\u002Fmetrics.go:16:7: &Metric{...} escapes to heap\n",[55,10024,10022],{"__ignoreMap":72},[45,10026,10027,10028,10031,10032,10034,10035,179],{},"Corpos idênticos, resultados opostos. Em ",[55,10029,10030],{},"IndirectCall",", o compilador não sabe qual função ",[55,10033,9956],{}," é, então não tem parameter tag para consultar e assume o pior. O mesmo acontece com chamadas de método por interface que a devirtualização não consegue resolver, e com ",[55,10036,10037],{},"reflect",[45,10039,10040,10045],{},[294,10041,7716,10042,10044],{},[55,10043,7278],{}," não é uma API estável."," Ela é uma ferramenta de diagnóstico do compilador; o formato e o conteúdo mudam entre versões. Não construa testes ou CI em cima dela.",[45,10047,10048,10051,10052,10055],{},[294,10049,10050],{},"Stack também não é de graça."," Frames grandes empurram a goroutine para perto do limite da stack, e crescer a stack significa alocar uma área maior e ",[294,10053,10054],{},"copiar"," a stack inteira, corrigindo os ponteiros internos. Trocar uma alocação de heap por um array de 100 KiB na stack de uma função recursiva pode piorar as coisas.",[45,10057,10058,10064,10065,10068],{},[294,10059,10060,10063],{},[55,10061,10062],{},"0 allocs\u002Fop"," não é sinônimo de rápido."," É um proxy para pressão de GC, que é uma das componentes do custo. Se ",[55,10066,10067],{},"ns\u002Fop"," não melhorou, a mudança não valeu.",[45,10070,10071,10074,10075,10077],{},[294,10072,10073],{},"Não escreva código estranho para agradar o analisador."," Isso é o mais importante da seção. Padrões como quebrar funções para forçar inlining, evitar interfaces em código frio ou substituir ",[55,10076,8687],{}," por concatenação manual em um endpoint chamado dez vezes por dia trocam legibilidade por nada. O critério continua sendo o profile.",[45,10079,10080,10086,10087,10090],{},[294,10081,10082,10085],{},[55,10083,10084],{},"sync.Pool"," não é um substituto."," Ele reduz a taxa de alocação reaproveitando objetos, mas não os tira do heap: a documentação diz explicitamente que \"qualquer item guardado no Pool pode ser removido automaticamente a qualquer momento\", e na prática o GC vai limpando o pool a cada ciclo (com um ",[344,10088,10089],{},"victim cache"," que dá a cada objeto um ciclo extra de sobrevida). É uma ferramenta para objetos caros e comprovadamente quentes, com o risco adicional de reter memória e de vazar dados entre usos se você esquecer de resetar o objeto.",[45,10092,10093,10099,10100,10102],{},[294,10094,10095,10098],{},[55,10096,10097],{},"unsafe.Pointer"," e stack allocation não se dão bem."," Como o compilador move valores entre stack e heap e o runtime copia stacks, código que faz aritmética de ponteiro fora das regras documentadas em ",[55,10101,10097],{}," pode quebrar quando uma alocação muda de lugar. As notas do Go 1.25 citam isso explicitamente ao introduzir mais alocações de slice na stack.",[315,10104,2732],{"id":2731},[45,10106,10107],{},"Os limites e otimizações citados aqui não são estáveis ao longo do tempo. As mudanças recentes que afetam este assunto:",[2734,10109,10110,10118],{},[2737,10111,10112],{},[2740,10113,10114,10116],{},[2743,10115,2745],{},[2743,10117,2748],{},[2750,10119,10120,10141,10153],{},[2740,10121,10122,10124],{},[2755,10123,352],{},[2755,10125,10126,10127,10130,10131,10134,10135,2780,10138,179],{},"O limite de declarações explícitas na stack (",[55,10128,10129],{},"MaxStackVarSize",") caiu de 10 MB para 128 KiB; o implícito seguiu em 64 KiB. Mudança interna, não citada nas release notes: dá para conferir comparando o ",[55,10132,10133],{},"cfg.go"," das tags ",[55,10136,10137],{},"go1.23.0",[55,10139,10140],{},"go1.24.0",[2740,10142,10143,10145],{},[2755,10144,6460],{},[2755,10146,10147,10148,10150,10151,179],{},"O compilador passou a alocar o backing store de slices na stack em mais situações, incluindo ",[55,10149,775],{}," com tamanho variável. Pode ser desligado com ",[55,10152,6475],{},[2740,10154,10155,10157],{},[2755,10156,296],{},[2755,10158,10159],{},"O coletor Green Tea virou o padrão, com redução esperada de 10 a 40% no overhead de GC. Não muda o que escapa, mas muda o custo de escapar.",[45,10161,10162,10163,10166],{},"Vale destacar o último ponto. Green Tea não altera nenhuma decisão de escape analysis; ele reduz o custo de ",[344,10164,10165],{},"manter"," dados no heap, que é a parte do overhead que sobra depois da alocação. Isso empurra ainda mais a régua na direção de \"meça antes de otimizar\": uma micro-otimização que valia a pena em um projeto de dois anos atrás pode não valer mais, e a única forma de saber é medir na versão que você está usando.",[315,10168,2858],{"id":2857},[45,10170,2861],{},[2863,10172,10173,10179,10195,10204,10223],{},[2866,10174,10175,10178],{},[294,10176,10177],{},"Esse código está em um caminho quente?"," Se não está, pare aqui. Escape analysis é assunto para o 1% do código que o profile aponta.",[2866,10180,10181,417,10184,10186,10187,3310,10189,10191,10192,10194],{},[294,10182,10183],{},"Quantas alocações por operação?",[55,10185,6370],{}," primeiro, ",[55,10188,6374],{},[55,10190,9745],{}," depois. Só então ",[55,10193,7272],{},", e só no pacote suspeito.",[2866,10196,10197,10200,10201,10203],{},[294,10198,10199],{},"A alocação é necessária ou acidental?"," Se o dado precisa sobreviver à chamada, ela é necessária e você está no lugar errado. Acidentais são quase sempre boxing em ",[55,10202,8369],{},", closures em goroutines ou um ponteiro entrando em uma estrutura de vida longa.",[2866,10205,10206,417,10209,10212,10213,2701,10216,10218,10219,10222],{},[294,10207,10208],{},"Existe uma API que evita o boxing?",[55,10210,10211],{},"slog.LogAttrs"," em vez de chaves soltas, ",[55,10214,10215],{},"strconv.Append*",[55,10217,8687],{},", um buffer local em vez de um ",[55,10220,10221],{},"io.Writer"," genérico.",[2866,10224,10225,10233],{},[294,10226,10227,10228,10230,10231,2931],{},"A mudança melhorou ",[55,10229,10067],{},", e não só ",[55,10232,6366],{}," Se não melhorou, reverta e mantenha a versão legível.",[45,10235,10236,10237,10239],{},"O que eu levaria deste artigo, no fim, é uma inversão de perspectiva. Escape analysis não é uma otimização que você aplica; é uma garantia de correção que o compilador te dá de graça, e que por acaso também é rápida na maior parte das vezes. Retornar ",[55,10238,8127],{}," em Go é seguro pelo mesmo motivo pelo qual é eficiente: o compilador entende o tempo de vida dos seus dados melhor do que a sintaxe do seu código consegue expressar. Da mesma forma que os zero values tiraram de você a obrigação de lembrar de inicializar, o escape analysis tira a obrigação de decidir onde as coisas moram. Nos dois casos, o ganho é o mesmo: menos decisões para errar.",[315,10241,2949],{"id":2948},[2951,10243,10244,10251,10259,10276,10286,10298,10313,10335,10345,10352,10371,10386,10396],{},[2866,10245,10246,10250],{},[49,10247,10249],{"href":7022,"rel":10248},[341],"Go FAQ — How do I know whether a variable is allocated on the heap or the stack?"," — a posição oficial sobre o assunto, e o critério que o compilador usa.",[2866,10252,10253,10258],{},[49,10254,10256],{"href":7054,"rel":10255},[341],[55,10257,7058],{}," — o comentário no topo do arquivo descreve as duas invariantes, o grafo de fluxo de dados e as parameter tags.",[2866,10260,10261,10268,10269,10271,10272,10275],{},[49,10262,10265],{"href":10263,"rel":10264},"https:\u002F\u002Fgithub.com\u002Fgolang\u002Fgo\u002Fblob\u002Fmaster\u002Fsrc\u002Fcmd\u002Fcompile\u002Finternal\u002Fir\u002Fcfg.go",[341],[55,10266,10267],{},"cmd\u002Fcompile\u002Finternal\u002Fir\u002Fcfg.go"," — os limites ",[55,10270,10129],{}," (128 KiB) e ",[55,10273,10274],{},"MaxImplicitStackVarSize"," (64 KiB).",[2866,10277,10278,10285],{},[49,10279,10282],{"href":10280,"rel":10281},"https:\u002F\u002Fgithub.com\u002Fgolang\u002Fgo\u002Fblob\u002Fmaster\u002Fsrc\u002Fcmd\u002Fcompile\u002Finternal\u002Fgc\u002Fmain.go",[341],[55,10283,10284],{},"cmd\u002Fcompile\u002Finternal\u002Fgc\u002Fmain.go"," — a ordem das etapas do compilador; devirtualização e inlining rodam antes da escape analysis.",[2866,10287,10288,3098,10295,10297],{},[49,10289,10292],{"href":10290,"rel":10291},"https:\u002F\u002Fgithub.com\u002Fgolang\u002Fgo\u002Fblob\u002Fmaster\u002Fsrc\u002Fruntime\u002Fstack.go",[341],[55,10293,10294],{},"runtime\u002Fstack.go",[55,10296,6985],{},", o tamanho inicial de 2 KiB das stacks de goroutine e o mecanismo de crescimento por cópia.",[2866,10299,10300,3098,10306,10309,10310,10312],{},[49,10301,10304],{"href":10302,"rel":10303},"https:\u002F\u002Fgithub.com\u002Fgolang\u002Fgo\u002Fblob\u002Fmaster\u002Fsrc\u002Fruntime\u002Fiface.go",[341],[55,10305,8681],{},[55,10307,10308],{},"convT64"," e o array ",[55,10311,8678],{},", que evita a alocação ao empacotar inteiros menores que 256 em uma interface.",[2866,10314,10315,2780,10324,10331,10332,10334],{},[49,10316,10319,10321,10322],{"href":10317,"rel":10318},"https:\u002F\u002Fgithub.com\u002Fgolang\u002Fgo\u002Fblob\u002Fgo1.23.0\u002Fsrc\u002Fcmd\u002Fcompile\u002Finternal\u002Fir\u002Fcfg.go",[341],[55,10320,10133],{}," na tag ",[55,10323,10137],{},[49,10325,10328,10329],{"href":10326,"rel":10327},"https:\u002F\u002Fgithub.com\u002Fgolang\u002Fgo\u002Fblob\u002Fgo1.24.0\u002Fsrc\u002Fcmd\u002Fcompile\u002Finternal\u002Fir\u002Fcfg.go",[341],"na tag ",[55,10330,10140],{}," — a comparação que mostra ",[55,10333,10129],{}," indo de 10 MB para 128 KiB.",[2866,10336,10337,10342,10343,179],{},[49,10338,10341],{"href":10339,"rel":10340},"https:\u002F\u002Fgo.dev\u002Fdoc\u002Fgo1.25#compiler",[341],"Go 1.25 Release Notes — Compiler"," — alocação na stack do backing store de slices em mais situações, e a flag ",[55,10344,6738],{},[2866,10346,10347,10351],{},[49,10348,10350],{"href":6743,"rel":10349},[341],"Go 1.26 Release Notes — Green Tea GC"," — o novo coletor como padrão e o impacto esperado no overhead de GC.",[2866,10353,10354,6602,10360,7699,10362,10364,10365,10368,10369,179],{},[49,10355,10358],{"href":10356,"rel":10357},"https:\u002F\u002Fpkg.go.dev\u002Fcmd\u002Fgo#hdr-Compile_packages_and_dependencies",[341],[55,10359,7706],{},[55,10361,7698],{},[55,10363,7702],{}," e por que ",[55,10366,10367],{},"all=-m"," é diferente de ",[55,10370,7278],{},[2866,10372,10373,10381,10382,10385],{},[49,10374,10377,10380],{"href":10375,"rel":10376},"https:\u002F\u002Fpkg.go.dev\u002Flog\u002Fslog#hdr-Performance_considerations",[341],[55,10378,10379],{},"log\u002Fslog"," — Performance considerations"," — a recomendação oficial de ",[55,10383,10384],{},"Logger.LogAttrs"," para evitar alocações, e outras dicas de custo em logging.",[2866,10387,10388,10395],{},[49,10389,10392,10393],{"href":10390,"rel":10391},"https:\u002F\u002Fpkg.go.dev\u002Funsafe#Pointer",[341],"Documentação do ",[55,10394,10097],{}," — os padrões válidos de conversão, e por que sair deles quebra na presença de stack copying.",[2866,10397,10398,10403,10404,10406],{},[49,10399,10402],{"href":10400,"rel":10401},"https:\u002F\u002Fgo.dev\u002Fblog\u002Fpprof",[341],"Profiling Go Programs"," — o fluxo de trabalho com ",[55,10405,6374],{}," no blog oficial.",[3129,10408,10409],{},"html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .swQdS, html code.shiki .swQdS{--shiki-light:#E53935;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .stzsN, html code.shiki .stzsN{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":72,"searchDepth":97,"depth":97,"links":10411},[10412,10413,10414,10418,10425,10426,10427,10428,10429],{"id":6950,"depth":97,"text":6951},{"id":6989,"depth":97,"text":6990},{"id":7268,"depth":97,"text":10415,"children":10416},"Parte 2: enxergando a decisão com -gcflags=-m",[10417],{"id":7712,"depth":119,"text":7713},{"id":8113,"depth":97,"text":8114,"children":10419},[10420,10421,10422,10423,10424],{"id":8120,"depth":119,"text":8121},{"id":8359,"depth":119,"text":8360},{"id":8992,"depth":119,"text":8993},{"id":9019,"depth":119,"text":9020},{"id":9333,"depth":119,"text":9334},{"id":9626,"depth":97,"text":9627},{"id":2421,"depth":97,"text":2422},{"id":2731,"depth":97,"text":2732},{"id":2857,"depth":97,"text":2858},{"id":2948,"depth":97,"text":2949},"2026-05-27","Como o compilador do Go decide se um valor fica na stack ou vai para o heap: o que é escape analysis, como ler a saída do -gcflags=-m, o que faz um valor escapar e o que realmente vale otimizar.",{},{"title":14,"description":10431},"uHKwFeKldxj4L9ChvsaJyRZv7SRBQJeRtPTXEarpOtM",{"id":10436,"title":18,"author":29,"body":10437,"date":11936,"description":11937,"extension":3148,"image":3149,"meta":11938,"minRead":586,"navigation":33,"path":19,"seo":11939,"stem":20,"tags":29,"updatedAt":29,"__hash__":11940},"blog\u002Fblog\u002Fgo-zero-values-e-value-semantics-vs-pointer-semantics.md",{"type":42,"value":10438,"toc":11921},[10439,10451,10454,10458,10461,10499,10508,10513,10584,10591,10594,10607,10610,10614,10617,10699,10706,10709,10776,10782,10867,10873,10877,10884,10889,10929,10934,10976,10983,11004,11007,11062,11071,11075,11085,11184,11199,11327,11334,11338,11345,11358,11482,11499,11515,11518,11532,11536,11543,11603,11626,11649,11653,11656,11663,11666,11726,11732,11745,11749,11758,11797,11811,11813,11816,11850,11853,11855,11918],[45,10440,10441,10442,10444,10445,2072,10448,179],{},"Se você está começando com Go vindo de outra linguagem, provavelmente já esbarrou em duas coisas que parecem simples mas mudam a forma como você escreve código: os ",[294,10443,6573],{}," e a decisão entre passar dados ",[294,10446,10447],{},"por valor",[294,10449,10450],{},"por ponteiro",[45,10452,10453],{},"À primeira vista são dois assuntos separados. Mas eles fazem parte da mesma filosofia da linguagem, e entender um ajuda a entender o outro. A proposta deste artigo é construir esse entendimento de forma gradual: primeiro o que é um zero value e por que ele existe, depois o que significam value e pointer semantics.",[315,10455,10457],{"id":10456},"a-filosofia-do-go-variável-declarada-é-variável-pronta","A filosofia do Go: variável declarada é variável pronta!",[45,10459,10460],{},"Em muitas linguagens, declarar uma variável e não atribuir um valor a ela é um problema. Veja em C:",[67,10462,10464],{"className":6823,"code":10463,"language":870,"meta":72,"style":72},"int x;\nprintf(\"%d\\n\", x); \u002F\u002F valor imprevisível (\"lixo de memória\")\n",[55,10465,10466,10474],{"__ignoreMap":72},[76,10467,10468,10470,10472],{"class":78,"line":79},[76,10469,762],{"class":109},[76,10471,7398],{"class":100},[76,10473,6860],{"class":93},[76,10475,10476,10479,10481,10483,10485,10487,10489,10491,10493,10496],{"class":78,"line":97},[76,10477,10478],{"class":153},"printf",[76,10480,157],{"class":93},[76,10482,261],{"class":260},[76,10484,1463],{"class":1456},[76,10486,1467],{"class":1466},[76,10488,261],{"class":260},[76,10490,518],{"class":93},[76,10492,7398],{"class":100},[76,10494,10495],{"class":93},");",[76,10497,10498],{"class":486}," \u002F\u002F valor imprevisível (\"lixo de memória\")\n",[45,10500,10501,10502,10504,10505,179],{},"Aqui ",[55,10503,6869],{}," foi declarada, mas não inicializada. O que existe naquele endereço de memória é o que sobrou de antes, um valor imprevisível. Ler uma variável automática não inicializada é justamente o que a especificação da linguagem chama de ",[344,10506,10507],{},"comportamento indefinido",[45,10509,10510,10511,179],{},"Em Java o problema aparece de outra forma: campos de referência são inicializados automaticamente como ",[55,10512,2704],{},[67,10514,10518],{"className":10515,"code":10516,"language":10517,"meta":72,"style":72},"language-java shiki shiki-themes material-theme-lighter github-light github-dark","class Sessao {\n    User user;           \u002F\u002F campo de referência começa como null\n\n    void processar() {\n        user.getName();  \u002F\u002F NullPointerException em tempo de execução\n    }\n}\n","java",[55,10519,10520,10530,10544,10548,10560,10576,10580],{"__ignoreMap":72},[76,10521,10522,10525,10528],{"class":78,"line":79},[76,10523,10524],{"class":109},"class",[76,10526,10527],{"class":86}," Sessao",[76,10529,94],{"class":93},[76,10531,10532,10536,10539,10541],{"class":78,"line":97},[76,10533,10535],{"class":10534},"s_bVq","    User",[76,10537,10538],{"class":100}," user",[76,10540,1805],{"class":93},[76,10542,10543],{"class":486},"           \u002F\u002F campo de referência começa como null\n",[76,10545,10546],{"class":78,"line":119},[76,10547,128],{"emptyLinePlaceholder":33},[76,10549,10550,10553,10556,10558],{"class":78,"line":125},[76,10551,10552],{"class":109},"    void",[76,10554,10555],{"class":153}," processar",[76,10557,723],{"class":93},[76,10559,94],{"class":93},[76,10561,10562,10565,10567,10570,10573],{"class":78,"line":131},[76,10563,10564],{"class":100},"        user",[76,10566,179],{"class":93},[76,10568,10569],{"class":153},"getName",[76,10571,10572],{"class":93},"();",[76,10574,10575],{"class":486},"  \u002F\u002F NullPointerException em tempo de execução\n",[76,10577,10578],{"class":78,"line":173},[76,10579,1146],{"class":93},[76,10581,10582],{"class":78,"line":194},[76,10583,122],{"class":93},[45,10585,10586,10587,10590],{},"O resultado é que você acaba escrevendo verificações defensivas em todo lugar (",[55,10588,10589],{},"if (user != null)",") para não quebrar em produção.",[45,10592,10593],{},"Go decidiu resolver isso na raiz com uma regra simples:",[288,10595,10596],{},[45,10597,10598,10599,10602,10603,10606],{},"Toda variável declarada em Go ",[294,10600,10601],{},"sempre"," recebe um valor inicial bem definido, mesmo que você não atribua nada. Esse valor é o ",[294,10604,10605],{},"zero value"," do tipo.",[45,10608,10609],{},"Não existe \"lixo de memória\" e não existe variável não inicializada. Isso não é um detalhe de implementação, é uma decisão de design pensada para que o código seja previsível por padrão.",[315,10611,10613],{"id":10612},"parte-1-o-que-são-zero-values","Parte 1: O que são Zero Values",[45,10615,10616],{},"Quando você declara uma variável sem valor, Go a inicializa com o zero value do tipo dela:",[67,10618,10620],{"className":69,"code":10619,"language":71,"meta":72,"style":72},"var age int       \u002F\u002F 0\nvar price float64   \u002F\u002F 0.0\nvar name string     \u002F\u002F \"\" (string vazia)\nvar active bool      \u002F\u002F false\nvar list []int     \u002F\u002F nil\nvar user *User   \u002F\u002F nil\n",[55,10621,10622,10633,10646,10658,10670,10684],{"__ignoreMap":72},[76,10623,10624,10626,10629,10631],{"class":78,"line":79},[76,10625,239],{"class":82},[76,10627,10628],{"class":100}," age ",[76,10630,762],{"class":109},[76,10632,510],{"class":486},[76,10634,10635,10637,10640,10643],{"class":78,"line":97},[76,10636,239],{"class":82},[76,10638,10639],{"class":100}," price ",[76,10641,10642],{"class":109},"float64",[76,10644,10645],{"class":486},"   \u002F\u002F 0.0\n",[76,10647,10648,10650,10653,10655],{"class":78,"line":119},[76,10649,239],{"class":82},[76,10651,10652],{"class":100}," name ",[76,10654,110],{"class":109},[76,10656,10657],{"class":486},"     \u002F\u002F \"\" (string vazia)\n",[76,10659,10660,10662,10665,10667],{"class":78,"line":125},[76,10661,239],{"class":82},[76,10663,10664],{"class":100}," active ",[76,10666,1063],{"class":109},[76,10668,10669],{"class":486},"      \u002F\u002F false\n",[76,10671,10672,10674,10677,10679,10681],{"class":78,"line":131},[76,10673,239],{"class":82},[76,10675,10676],{"class":100}," list ",[76,10678,3491],{"class":93},[76,10680,762],{"class":109},[76,10682,10683],{"class":486},"     \u002F\u002F nil\n",[76,10685,10686,10688,10691,10693,10696],{"class":78,"line":173},[76,10687,239],{"class":82},[76,10689,10690],{"class":100}," user ",[76,10692,144],{"class":82},[76,10694,10695],{"class":86},"User",[76,10697,10698],{"class":486},"   \u002F\u002F nil\n",[45,10700,10701,10702,10705],{},"O zero value é ",[294,10703,10704],{},"determinístico",": para um dado tipo, ele é sempre o mesmo. Isso significa que você pode raciocinar sobre o estado inicial de qualquer variável só olhando o tipo dela.",[45,10707,10708],{},"Aqui está a tabela que vale a pena guardar:",[2734,10710,10711,10720],{},[2737,10712,10713],{},[2740,10714,10715,10718],{},[2743,10716,10717],{},"Categoria de tipo",[2743,10719,3966],{},[2750,10721,10722,10736,10746,10757,10766],{},[2740,10723,10724,10732],{},[2755,10725,10726,10727,619,10729,10731],{},"Numéricos (",[55,10728,762],{},[55,10730,10642],{},", ...)",[2755,10733,10734],{},[55,10735,1006],{},[2740,10737,10738,10742],{},[2755,10739,10740],{},[55,10741,1063],{},[2755,10743,10744],{},[55,10745,1014],{},[2740,10747,10748,10752],{},[2755,10749,10750],{},[55,10751,110],{},[2755,10753,10754],{},[55,10755,10756],{},"\"\"",[2740,10758,10759,10762],{},[2755,10760,10761],{},"Ponteiros, slices, maps, channels, funcs, interfaces",[2755,10763,10764],{},[55,10765,61],{},[2740,10767,10768,10773],{},[2755,10769,10770],{},[55,10771,10772],{},"struct",[2755,10774,10775],{},"cada campo com seu próprio zero value",[45,10777,10778,10779,10781],{},"O caso do ",[55,10780,10772],{}," é o mais interessante, porque a regra se aplica recursivamente:",[67,10783,10785],{"className":69,"code":10784,"language":71,"meta":72,"style":72},"type User struct {\n    Nome  string\n    Idade int\n    Ativo bool\n}\n\nvar u User\nfmt.Printf(\"%+v\\n\", u) \u002F\u002F {Nome: Idade:0 Ativo:false}\n",[55,10786,10787,10798,10805,10812,10820,10824,10828,10838],{"__ignoreMap":72},[76,10788,10789,10791,10794,10796],{"class":78,"line":79},[76,10790,83],{"class":82},[76,10792,10793],{"class":86}," User",[76,10795,90],{"class":82},[76,10797,94],{"class":93},[76,10799,10800,10803],{"class":78,"line":97},[76,10801,10802],{"class":100},"    Nome  ",[76,10804,818],{"class":109},[76,10806,10807,10810],{"class":78,"line":119},[76,10808,10809],{"class":100},"    Idade ",[76,10811,116],{"class":109},[76,10813,10814,10817],{"class":78,"line":125},[76,10815,10816],{"class":100},"    Ativo ",[76,10818,10819],{"class":109},"bool\n",[76,10821,10822],{"class":78,"line":131},[76,10823,122],{"class":93},[76,10825,10826],{"class":78,"line":173},[76,10827,128],{"emptyLinePlaceholder":33},[76,10829,10830,10832,10835],{"class":78,"line":194},[76,10831,239],{"class":82},[76,10833,10834],{"class":100}," u ",[76,10836,10837],{"class":86},"User\n",[76,10839,10840,10842,10844,10846,10848,10850,10853,10855,10857,10859,10862,10864],{"class":78,"line":221},[76,10841,461],{"class":100},[76,10843,179],{"class":93},[76,10845,1449],{"class":153},[76,10847,157],{"class":93},[76,10849,261],{"class":260},[76,10851,10852],{"class":1456},"%+v",[76,10854,1467],{"class":1466},[76,10856,261],{"class":260},[76,10858,518],{"class":93},[76,10860,10861],{"class":100}," u",[76,10863,150],{"class":93},[76,10865,10866],{"class":486}," \u002F\u002F {Nome: Idade:0 Ativo:false}\n",[45,10868,10869,10870,10872],{},"Não precisei escrever um construtor nem inicializar campo por campo. A ",[55,10871,10772],{}," já nasce em um estado válido e conhecido.",[1301,10874,10876],{"id":10875},"o-padrão-do-zero-value-útil","O padrão do \"zero value útil\"",[45,10878,10879,10880,10883],{},"Em Go, um bom design faz o zero value ser ",[294,10881,10882],{},"imediatamente utilizável",", sem exigir inicialização.",[45,10885,10886,10887,359],{},"O exemplo clássico é ",[55,10888,2071],{},[67,10890,10892],{"className":69,"code":10891,"language":71,"meta":72,"style":72},"var mu sync.Mutex\nmu.Lock()   \u002F\u002F funciona. Não precisou de sync.NewMutex()\nmu.Unlock()\n",[55,10893,10894,10906,10919],{"__ignoreMap":72},[76,10895,10896,10898,10900,10902,10904],{"class":78,"line":79},[76,10897,239],{"class":82},[76,10899,9116],{"class":100},[76,10901,2060],{"class":86},[76,10903,179],{"class":93},[76,10905,9123],{"class":86},[76,10907,10908,10910,10912,10914,10916],{"class":78,"line":97},[76,10909,2175],{"class":100},[76,10911,179],{"class":93},[76,10913,2292],{"class":153},[76,10915,723],{"class":93},[76,10917,10918],{"class":486},"   \u002F\u002F funciona. Não precisou de sync.NewMutex()\n",[76,10920,10921,10923,10925,10927],{"class":78,"line":119},[76,10922,2175],{"class":100},[76,10924,179],{"class":93},[76,10926,2310],{"class":153},[76,10928,1270],{"class":93},[45,10930,6030,10931,359],{},[55,10932,10933],{},"bytes.Buffer",[67,10935,10937],{"className":69,"code":10936,"language":71,"meta":72,"style":72},"var buf bytes.Buffer\nbuf.WriteString(\"olá\") \u002F\u002F já funciona, buffer vazio é um estado válido\n",[55,10938,10939,10953],{"__ignoreMap":72},[76,10940,10941,10943,10945,10948,10950],{"class":78,"line":79},[76,10942,239],{"class":82},[76,10944,9503],{"class":100},[76,10946,10947],{"class":86},"bytes",[76,10949,179],{"class":93},[76,10951,10952],{"class":86},"Buffer\n",[76,10954,10955,10957,10959,10962,10964,10966,10969,10971,10973],{"class":78,"line":97},[76,10956,8825],{"class":100},[76,10958,179],{"class":93},[76,10960,10961],{"class":153},"WriteString",[76,10963,157],{"class":93},[76,10965,261],{"class":260},[76,10967,10968],{"class":264},"olá",[76,10970,261],{"class":260},[76,10972,150],{"class":93},[76,10974,10975],{"class":486}," \u002F\u002F já funciona, buffer vazio é um estado válido\n",[45,10977,10978,10979,10982],{},"Quando você projetar suas próprias structs, vale perguntar: ",[344,10980,10981],{},"\"o zero value dessa struct faz sentido sozinho?\"",". Se a resposta for sim, você poupa quem usa seu código de uma etapa de inicialização.",[985,10984,10985],{},[45,10986,10987,10988,10990,10991,10993,10994,10996,10997,10999,11000,11003],{},"Nem todo zero value é útil de imediato. Um ",[55,10989,57],{}," com zero value é ",[55,10992,61],{},", e escrever em um map ",[55,10995,61],{}," causa ",[294,10998,1970],{},". Nesses casos você ainda precisa inicializar com ",[55,11001,11002],{},"make(map[string]int)",". Ler de um map nil, porém, é seguro e retorna o zero value.",[45,11005,11006],{},"Antes de seguir, uma armadilha comum que combina zero value com um dos temas do próximo tópico:",[67,11008,11010],{"className":69,"code":11009,"language":71,"meta":72,"style":72},"var list []int\nlist = append(list, 1) \u002F\u002F OK! append lida com slice nil de boa\nfmt.Println(list)        \u002F\u002F [1]\n",[55,11011,11012,11022,11045],{"__ignoreMap":72},[76,11013,11014,11016,11018,11020],{"class":78,"line":79},[76,11015,239],{"class":82},[76,11017,10676],{"class":100},[76,11019,3491],{"class":93},[76,11021,116],{"class":109},[76,11023,11024,11027,11029,11031,11033,11036,11038,11040,11042],{"class":78,"line":97},[76,11025,11026],{"class":100},"list ",[76,11028,1126],{"class":82},[76,11030,1129],{"class":153},[76,11032,157],{"class":93},[76,11034,11035],{"class":100},"list",[76,11037,518],{"class":93},[76,11039,1561],{"class":217},[76,11041,150],{"class":93},[76,11043,11044],{"class":486}," \u002F\u002F OK! append lida com slice nil de boa\n",[76,11046,11047,11049,11051,11053,11055,11057,11059],{"class":78,"line":119},[76,11048,461],{"class":100},[76,11050,179],{"class":93},[76,11052,466],{"class":153},[76,11054,157],{"class":93},[76,11056,11035],{"class":100},[76,11058,150],{"class":93},[76,11060,11061],{"class":486},"        \u002F\u002F [1]\n",[45,11063,11064,11065,11067,11068,11070],{},"Um slice ",[55,11066,61],{}," tem comprimento zero e você pode dar ",[55,11069,696],{}," nele normalmente. Isso é diferente do map, e é o tipo de detalhe que só fica natural com o tempo.",[315,11072,11074],{"id":11073},"parte-2-value-semantics","Parte 2: Value Semantics",[45,11076,11077,11078,11081,11082,179],{},"Agora vamos à segunda metade. Em Go, quando você passa um valor para uma função ou atribui a outra variável, ",[294,11079,11080],{},"por padrão uma cópia é feita",". Isso é o que chamamos de ",[344,11083,11084],{},"value semantics",[67,11086,11088],{"className":69,"code":11087,"language":71,"meta":72,"style":72},"func dobrar(n int) {\n    n = n * 2 \u002F\u002F altera a CÓPIA local\n}\n\nfunc main() {\n    x := 10\n    dobrar(x)\n    fmt.Println(x) \u002F\u002F 10 — o original não mudou\n}\n",[55,11089,11090,11107,11124,11128,11132,11142,11152,11163,11180],{"__ignoreMap":72},[76,11091,11092,11094,11097,11099,11101,11103,11105],{"class":78,"line":79},[76,11093,134],{"class":82},[76,11095,11096],{"class":153}," dobrar",[76,11098,157],{"class":93},[76,11100,9600],{"class":140},[76,11102,2274],{"class":109},[76,11104,150],{"class":93},[76,11106,94],{"class":93},[76,11108,11109,11112,11114,11117,11119,11121],{"class":78,"line":97},[76,11110,11111],{"class":100},"    n ",[76,11113,1126],{"class":82},[76,11115,11116],{"class":100}," n ",[76,11118,144],{"class":82},[76,11120,1575],{"class":217},[76,11122,11123],{"class":486}," \u002F\u002F altera a CÓPIA local\n",[76,11125,11126],{"class":78,"line":119},[76,11127,122],{"class":93},[76,11129,11130],{"class":78,"line":125},[76,11131,128],{"emptyLinePlaceholder":33},[76,11133,11134,11136,11138,11140],{"class":78,"line":131},[76,11135,134],{"class":82},[76,11137,3460],{"class":153},[76,11139,723],{"class":93},[76,11141,94],{"class":93},[76,11143,11144,11147,11149],{"class":78,"line":173},[76,11145,11146],{"class":100},"    x ",[76,11148,524],{"class":82},[76,11150,11151],{"class":217}," 10\n",[76,11153,11154,11157,11159,11161],{"class":78,"line":194},[76,11155,11156],{"class":153},"    dobrar",[76,11158,157],{"class":93},[76,11160,6869],{"class":100},[76,11162,270],{"class":93},[76,11164,11165,11167,11169,11171,11173,11175,11177],{"class":78,"line":221},[76,11166,1444],{"class":100},[76,11168,179],{"class":93},[76,11170,466],{"class":153},[76,11172,157],{"class":93},[76,11174,6869],{"class":100},[76,11176,150],{"class":93},[76,11178,11179],{"class":486}," \u002F\u002F 10 — o original não mudou\n",[76,11181,11182],{"class":78,"line":581},[76,11183,122],{"class":93},[45,11185,11186,11187,11190,11191,11193,11194,7699,11196,11198],{},"A função ",[55,11188,11189],{},"dobrar"," recebeu uma cópia de ",[55,11192,6869],{},". Alterá-la lá dentro não tem efeito nenhum sobre o ",[55,11195,6869],{},[55,11197,4202],{},". O mesmo acontece com structs:",[67,11200,11202],{"className":69,"code":11201,"language":71,"meta":72,"style":72},"func aniversario(u User) {\n    u.Idade++ \u002F\u002F altera a cópia\n}\n\nfunc main() {\n    joao := User{Nome: \"João\", Idade: 30}\n    aniversario(joao)\n    fmt.Println(joao.Idade) \u002F\u002F 30 — inalterado\n}\n",[55,11203,11204,11222,11237,11241,11245,11255,11290,11302,11323],{"__ignoreMap":72},[76,11205,11206,11208,11211,11213,11216,11218,11220],{"class":78,"line":79},[76,11207,134],{"class":82},[76,11209,11210],{"class":153}," aniversario",[76,11212,157],{"class":93},[76,11214,11215],{"class":140},"u",[76,11217,10793],{"class":86},[76,11219,150],{"class":93},[76,11221,94],{"class":93},[76,11223,11224,11227,11229,11232,11234],{"class":78,"line":97},[76,11225,11226],{"class":100},"    u",[76,11228,179],{"class":93},[76,11230,11231],{"class":100},"Idade",[76,11233,1830],{"class":82},[76,11235,11236],{"class":486}," \u002F\u002F altera a cópia\n",[76,11238,11239],{"class":78,"line":119},[76,11240,122],{"class":93},[76,11242,11243],{"class":78,"line":125},[76,11244,128],{"emptyLinePlaceholder":33},[76,11246,11247,11249,11251,11253],{"class":78,"line":131},[76,11248,134],{"class":82},[76,11250,3460],{"class":153},[76,11252,723],{"class":93},[76,11254,94],{"class":93},[76,11256,11257,11260,11262,11264,11266,11269,11271,11273,11276,11278,11280,11283,11285,11288],{"class":78,"line":173},[76,11258,11259],{"class":100},"    joao ",[76,11261,524],{"class":82},[76,11263,10793],{"class":86},[76,11265,742],{"class":93},[76,11267,11268],{"class":100},"Nome",[76,11270,359],{"class":93},[76,11272,598],{"class":260},[76,11274,11275],{"class":264},"João",[76,11277,261],{"class":260},[76,11279,518],{"class":93},[76,11281,11282],{"class":100}," Idade",[76,11284,359],{"class":93},[76,11286,11287],{"class":217}," 30",[76,11289,122],{"class":93},[76,11291,11292,11295,11297,11300],{"class":78,"line":194},[76,11293,11294],{"class":153},"    aniversario",[76,11296,157],{"class":93},[76,11298,11299],{"class":100},"joao",[76,11301,270],{"class":93},[76,11303,11304,11306,11308,11310,11312,11314,11316,11318,11320],{"class":78,"line":221},[76,11305,1444],{"class":100},[76,11307,179],{"class":93},[76,11309,466],{"class":153},[76,11311,157],{"class":93},[76,11313,11299],{"class":100},[76,11315,179],{"class":93},[76,11317,11231],{"class":100},[76,11319,150],{"class":93},[76,11321,11322],{"class":486}," \u002F\u002F 30 — inalterado\n",[76,11324,11325],{"class":78,"line":581},[76,11326,122],{"class":93},[45,11328,11329,11330,11333],{},"A vantagem de value semantics é o ",[294,11331,11332],{},"isolamento",": cada função trabalha com seu próprio dado e não há como uma parte do código modificar acidentalmente um valor que pertence a outra. O código fica mais fácil de raciocinar, porque você sabe que passar algo adiante não vai alterá-lo pelas suas costas.",[315,11335,11337],{"id":11336},"parte-3-pointer-semantics","Parte 3: Pointer Semantics",[45,11339,11340,11341,11344],{},"Mas e quando você ",[344,11342,11343],{},"quer"," que a alteração seja refletida no original? Ou quando copiar o dado inteiro seria caro? É aí que entram os ponteiros.",[45,11346,11347,11348,11351,11352,11354,11355,11357],{},"Um ponteiro guarda o ",[294,11349,11350],{},"endereço"," de um valor, não o valor em si. Usamos ",[55,11353,7008],{}," para pegar o endereço de uma variável e ",[55,11356,144],{}," para acessar o valor apontado.",[67,11359,11361],{"className":69,"code":11360,"language":71,"meta":72,"style":72},"func aniversario(u *User) {\n    u.Idade++ \u002F\u002F altera o ORIGINAL, através do endereço\n}\n\nfunc main() {\n    joao := User{Nome: \"João\", Idade: 30}\n    aniversario(&joao)      \u002F\u002F passamos o endereço\n    fmt.Println(joao.Idade) \u002F\u002F 31 — mudou de verdade\n}\n",[55,11362,11363,11381,11394,11398,11402,11412,11442,11457,11478],{"__ignoreMap":72},[76,11364,11365,11367,11369,11371,11373,11375,11377,11379],{"class":78,"line":79},[76,11366,134],{"class":82},[76,11368,11210],{"class":153},[76,11370,157],{"class":93},[76,11372,11215],{"class":140},[76,11374,726],{"class":82},[76,11376,10695],{"class":86},[76,11378,150],{"class":93},[76,11380,94],{"class":93},[76,11382,11383,11385,11387,11389,11391],{"class":78,"line":97},[76,11384,11226],{"class":100},[76,11386,179],{"class":93},[76,11388,11231],{"class":100},[76,11390,1830],{"class":82},[76,11392,11393],{"class":486}," \u002F\u002F altera o ORIGINAL, através do endereço\n",[76,11395,11396],{"class":78,"line":119},[76,11397,122],{"class":93},[76,11399,11400],{"class":78,"line":125},[76,11401,128],{"emptyLinePlaceholder":33},[76,11403,11404,11406,11408,11410],{"class":78,"line":131},[76,11405,134],{"class":82},[76,11407,3460],{"class":153},[76,11409,723],{"class":93},[76,11411,94],{"class":93},[76,11413,11414,11416,11418,11420,11422,11424,11426,11428,11430,11432,11434,11436,11438,11440],{"class":78,"line":173},[76,11415,11259],{"class":100},[76,11417,524],{"class":82},[76,11419,10793],{"class":86},[76,11421,742],{"class":93},[76,11423,11268],{"class":100},[76,11425,359],{"class":93},[76,11427,598],{"class":260},[76,11429,11275],{"class":264},[76,11431,261],{"class":260},[76,11433,518],{"class":93},[76,11435,11282],{"class":100},[76,11437,359],{"class":93},[76,11439,11287],{"class":217},[76,11441,122],{"class":93},[76,11443,11444,11446,11448,11450,11452,11454],{"class":78,"line":194},[76,11445,11294],{"class":153},[76,11447,157],{"class":93},[76,11449,7008],{"class":82},[76,11451,11299],{"class":100},[76,11453,150],{"class":93},[76,11455,11456],{"class":486},"      \u002F\u002F passamos o endereço\n",[76,11458,11459,11461,11463,11465,11467,11469,11471,11473,11475],{"class":78,"line":221},[76,11460,1444],{"class":100},[76,11462,179],{"class":93},[76,11464,466],{"class":153},[76,11466,157],{"class":93},[76,11468,11299],{"class":100},[76,11470,179],{"class":93},[76,11472,11231],{"class":100},[76,11474,150],{"class":93},[76,11476,11477],{"class":486}," \u002F\u002F 31 — mudou de verdade\n",[76,11479,11480],{"class":78,"line":581},[76,11481,122],{"class":93},[45,11483,11484,11485,11487,11488,11491,11492,11494,11495,11498],{},"Agora a função recebeu o endereço de ",[55,11486,11299],{},". Ao alterar ",[55,11489,11490],{},"u.Idade",", ela está mexendo no mesmo dado que existe no ",[55,11493,4202],{},". Isso é ",[344,11496,11497],{},"pointer semantics",": você compartilha o acesso ao valor em vez de copiá-lo.",[288,11500,11501],{},[45,11502,11503,11504,11506,11507,11510,11511,11514],{},"Note que em ",[55,11505,11490],{}," não precisei escrever ",[55,11508,11509],{},"(*u).Idade",". Go faz esse \"de-referenciamento\" automaticamente para campos de struct acessados por ponteiro. É um ",[344,11512,11513],{},"syntactic sugar"," que deixa o código mais limpo.",[45,11516,11517],{},"Existem dois motivos principais para usar ponteiros:",[2863,11519,11520,11526],{},[2866,11521,11522,11525],{},[294,11523,11524],{},"Mutação compartilhada",": quando a função precisa modificar o valor original.",[2866,11527,11528,11531],{},[294,11529,11530],{},"Evitar cópias caras",": quando a struct é grande, copiá-la a cada chamada custa memória e tempo.",[1301,11533,11535],{"id":11534},"um-detalhe-que-muda-tudo-ponteiro-também-é-valor","Um detalhe que muda tudo: ponteiro também é valor",[45,11537,11538,11539,11542],{},"Aqui vai a sacada que costuma reorganizar o entendimento: em Go, tudo é passado por valor, sempre. Não existe \"passagem por referência\" como em algumas outras linguagens. Quando você passa um ponteiro, o que acontece é que o ",[344,11540,11541],{},"valor do ponteiro"," (ou seja, o endereço) é copiado.",[67,11544,11546],{"className":69,"code":11545,"language":71,"meta":72,"style":72},"u := User{Nome: \"João\"}\n\np := &u   \u002F\u002F p guarda o endereço de u\nq := p    \u002F\u002F q é uma CÓPIA do endereço, não \"referencia\" p\n",[55,11547,11548,11571,11575,11590],{"__ignoreMap":72},[76,11549,11550,11553,11555,11557,11559,11561,11563,11565,11567,11569],{"class":78,"line":79},[76,11551,11552],{"class":100},"u ",[76,11554,524],{"class":82},[76,11556,10793],{"class":86},[76,11558,742],{"class":93},[76,11560,11268],{"class":100},[76,11562,359],{"class":93},[76,11564,598],{"class":260},[76,11566,11275],{"class":264},[76,11568,261],{"class":260},[76,11570,122],{"class":93},[76,11572,11573],{"class":78,"line":97},[76,11574,128],{"emptyLinePlaceholder":33},[76,11576,11577,11580,11582,11584,11587],{"class":78,"line":119},[76,11578,11579],{"class":100},"p ",[76,11581,524],{"class":82},[76,11583,737],{"class":82},[76,11585,11586],{"class":100},"u   ",[76,11588,11589],{"class":486},"\u002F\u002F p guarda o endereço de u\n",[76,11591,11592,11595,11597,11600],{"class":78,"line":125},[76,11593,11594],{"class":100},"q ",[76,11596,524],{"class":82},[76,11598,11599],{"class":100}," p    ",[76,11601,11602],{"class":486},"\u002F\u002F q é uma CÓPIA do endereço, não \"referencia\" p\n",[45,11604,11605,11608,11609,6997,11611,11613,11614,11616,11617,11619,11620,11622,11623,11625],{},[55,11606,11607],{},"q"," não aponta para ",[55,11610,45],{},[55,11612,11607],{}," recebeu uma cópia do endereço que estava em ",[55,11615,45],{},". Os dois apontam para o mesmo ",[55,11618,10695],{},", mas são dois ponteiros independentes. No fundo, ",[344,11621,11497],{}," é só ",[344,11624,11084],{}," aplicada a um valor que por acaso é um endereço.",[45,11627,11628,11629,11631,11632,11634,11635,11638,11639,619,11641,619,11643,2780,11646,11648],{},"Isso também explica por que alguns tipos parecem se comportar de forma diferente. Quando você copia uma ",[55,11630,10772],{}," ou um ",[55,11633,3346],{},", copia o conteúdo inteiro: são valores ",[294,11636,11637],{},"autocontidos",". Já ",[55,11640,690],{},[55,11642,57],{},[55,11644,11645],{},"channel",[55,11647,134],{}," carregam uma referência interna, então copiar um slice copia apenas o seu cabeçalho (ponteiro, tamanho e capacidade), não o array por baixo. É por isso que dois slices podem enxergar os mesmos dados mesmo sem você usar ponteiro explicitamente. Esse comportamento rende um artigo só pra ele, e é o que pretendo destrinchar em um próximo post.",[315,11650,11652],{"id":11651},"parte-4-como-escolher-entre-value-e-pointer-semantics","Parte 4: Como escolher entre value e pointer semantics",[45,11654,11655],{},"Ao longo dos anos, a comunidade e os mantenedores do Go consolidaram algumas diretrizes bem práticas (Effective Go, o Code Review Comments do Go Wiki, e gente como Dave Cheney e William Kennedy).",[45,11657,11658,11659,11662],{},"A regra mais importante vem do próprio time do Go: ",[294,11660,11661],{},"seja consistente",". A recomendação do Go Wiki é clara: se algum método de um tipo precisa de receiver por ponteiro, então normalmente todos deveriam usar ponteiro. Misturar value e pointer no mesmo tipo sem necessidade é fonte de bugs sutis.",[45,11664,11665],{},"Como ponto de partida:",[985,11667,11668,11673,11699,11704],{},[45,11669,11670],{},[294,11671,11672],{},"Use pointer semantics quando:",[2951,11674,11675,11682,11689],{},[2866,11676,11677,11678,11681],{},"A função precisa ",[294,11679,11680],{},"modificar"," o valor recebido.",[2866,11683,11684,11685,11688],{},"A struct é ",[294,11686,11687],{},"grande"," e a cópia seria custosa.",[2866,11690,11691,11692,11695,11696,11698],{},"O tipo representa algo que ",[294,11693,11694],{},"não deve ser copiado"," (como um ",[55,11697,2071],{},", uma conexão, um buffer com estado).",[45,11700,11701],{},[294,11702,11703],{},"Use value semantics quando:",[2951,11705,11706,11716,11723],{},[2866,11707,11708,11709,11712,11713,3074],{},"O tipo é pequeno e representa um ",[294,11710,11711],{},"dado"," (um ponto, uma cor, um ",[55,11714,11715],{},"time.Time",[2866,11717,11718,11719,11722],{},"Você quer garantir ",[294,11720,11721],{},"imutabilidade"," e isolamento.",[2866,11724,11725],{},"Não há necessidade de modificar o original.",[45,11727,11728,11729,11731],{},"Um exemplo de tipo que idiomaticamente usa value semantics é ",[55,11730,11715],{},". Ele é passado por valor em toda a biblioteca padrão, mesmo sendo uma struct, porque representa um instante, um dado, não algo com estado mutável compartilhado.",[45,11733,11734,11735,619,11738,11741,11742,11744],{},"Já tipos como ",[55,11736,11737],{},"*sql.DB",[55,11739,11740],{},"*http.Request"," ou os que carregam um ",[55,11743,2071],{}," usam pointer semantics, porque copiá-los seria errado ou perigoso.",[1301,11746,11748],{"id":11747},"um-cuidado-importante-ponteiros-e-nil","Um cuidado importante: ponteiros e nil",[45,11750,11751,11752,11754,11755,11757],{},"Quando você adota pointer semantics, o zero value volta a aparecer e pode te passar uma rasteira. O zero value de um ponteiro é ",[55,11753,61],{},", e acessar um ponteiro ",[55,11756,61],{}," causa panic:",[67,11759,11761],{"className":69,"code":11760,"language":71,"meta":72,"style":72},"var u *User      \u002F\u002F nil\nfmt.Println(u.Nome) \u002F\u002F panic: invalid memory address\n",[55,11762,11763,11776],{"__ignoreMap":72},[76,11764,11765,11767,11769,11771,11773],{"class":78,"line":79},[76,11766,239],{"class":82},[76,11768,10834],{"class":100},[76,11770,144],{"class":82},[76,11772,10695],{"class":86},[76,11774,11775],{"class":486},"      \u002F\u002F nil\n",[76,11777,11778,11780,11782,11784,11786,11788,11790,11792,11794],{"class":78,"line":97},[76,11779,461],{"class":100},[76,11781,179],{"class":93},[76,11783,466],{"class":153},[76,11785,157],{"class":93},[76,11787,11215],{"class":100},[76,11789,179],{"class":93},[76,11791,11268],{"class":100},[76,11793,150],{"class":93},[76,11795,11796],{"class":486}," \u002F\u002F panic: invalid memory address\n",[45,11798,11799,11800,11803,11804,11807,11808,11810],{},"Repare no ciclo completo: foi justamente esse tipo de problema que os zero values resolveram para ",[344,11801,11802],{},"valores",", mas ele reaparece assim que você introduz ",[344,11805,11806],{},"ponteiros",". Por isso a escolha por pointer semantics não é gratuita, ela traz de volta a necessidade de pensar em ",[55,11809,61],{},". É mais um argumento a favor de preferir value semantics quando você não tem um motivo claro para o contrário.",[315,11812,2858],{"id":2857},[45,11814,11815],{},"Se eu tivesse que resumir o raciocínio em uma sequência de perguntas na hora de escrever código Go, seria assim:",[2863,11817,11818,11824,11830,11836,11842],{},[2866,11819,11820,11823],{},[294,11821,11822],{},"Qual o estado inicial natural do meu tipo?"," Se o zero value já for utilizável, aproveite, menos inicialização, menos código.",[2866,11825,11826,11829],{},[294,11827,11828],{},"Essa função precisa alterar o valor original?"," Se sim, ponteiro. Se não, comece pensando em valor.",[2866,11831,11832,11835],{},[294,11833,11834],{},"O valor é grande ou não pode ser copiado?"," Se sim, ponteiro, mesmo sem mutação.",[2866,11837,11838,11841],{},[294,11839,11840],{},"Estou sendo consistente com o resto do tipo?"," Se um método precisa de ponteiro, normalmente todos os outros também deveriam usar ponteiro.",[2866,11843,11844,11849],{},[294,11845,11846,11847,2931],{},"Ao usar ponteiros, tratei o caso ",[55,11848,61],{}," O zero value voltou; não deixe ele te pegar.",[45,11851,11852],{},"Zero values e semântica de passagem não são tópicos isolados: são duas faces da mesma ideia de que, em Go, o estado de um valor deve ser sempre previsível. Value semantics preserva o isolamento dos dados: cada parte do código trabalha com sua própria cópia. Pointer semantics abre mão desse isolamento de propósito, para permitir compartilhamento de estado entre diferentes partes do código, e é justamente por isso que exige mais cuidado.",[315,11854,2949],{"id":2948},[2951,11856,11857,11865,11878,11886,11894,11902,11910],{},[2866,11858,11859,11864],{},[49,11860,11863],{"href":11861,"rel":11862},"https:\u002F\u002Fgo.dev\u002Fref\u002Fspec#The_zero_value",[341],"The Go Programming Language Specification — The zero value"," — definição oficial de zero values na spec da linguagem.",[2866,11866,11867,11872,11873,619,11875,11877],{},[49,11868,11871],{"href":11869,"rel":11870},"https:\u002F\u002Fgo.dev\u002Fdoc\u002Feffective_go#data",[341],"Effective Go — Data \u002F Allocation"," — como o Go trata inicialização, ",[55,11874,7000],{},[55,11876,775],{}," e o padrão do zero value útil.",[2866,11879,11880,11885],{},[49,11881,11884],{"href":11882,"rel":11883},"https:\u002F\u002Fgo.dev\u002Fblog\u002Flaws-of-reflection",[341],"Go Blog — The Laws of Reflection"," — contexto sobre valores, tipos e como Go os representa internamente.",[2866,11887,11888,11893],{},[49,11889,11892],{"href":11890,"rel":11891},"https:\u002F\u002Fwww.ardanlabs.com\u002Fblog\u002F2017\u002F06\u002Fdesign-philosophy-on-data-and-semantics.html",[341],"Ardan Labs — Value and Pointer Semantics (William Kennedy)"," — a referência mais citada sobre a filosofia de escolher entre value e pointer semantics.",[2866,11895,11896,11901],{},[49,11897,11900],{"href":11898,"rel":11899},"https:\u002F\u002Fgo.dev\u002Fwiki\u002FCodeReviewComments#receiver-type",[341],"Go Wiki — Code Review Comments: Receiver Type"," — diretrizes oficiais sobre quando usar receivers por valor ou por ponteiro.",[2866,11903,11904,11909],{},[49,11905,11908],{"href":11906,"rel":11907},"https:\u002F\u002Fdave.cheney.net\u002F2016\u002F03\u002F19\u002Fshould-methods-be-declared-on-t-or-t",[341],"Dave Cheney — Should methods be declared on T or *T"," — discussão aprofundada sobre consistência de semântica em métodos.",[2866,11911,11912,11917],{},[49,11913,11916],{"href":11914,"rel":11915},"https:\u002F\u002Fgo.dev\u002Ftour\u002Fmoretypes\u002F1",[341],"A Tour of Go — Pointers"," — introdução interativa e oficial a ponteiros.",[3129,11919,11920],{},"html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .swQdS, html code.shiki .swQdS{--shiki-light:#E53935;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .s_bVq, html code.shiki .s_bVq{--shiki-light:#9C3EDA;--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":72,"searchDepth":97,"depth":97,"links":11922},[11923,11924,11927,11928,11931,11934,11935],{"id":10456,"depth":97,"text":10457},{"id":10612,"depth":97,"text":10613,"children":11925},[11926],{"id":10875,"depth":119,"text":10876},{"id":11073,"depth":97,"text":11074},{"id":11336,"depth":97,"text":11337,"children":11929},[11930],{"id":11534,"depth":119,"text":11535},{"id":11651,"depth":97,"text":11652,"children":11932},[11933],{"id":11747,"depth":119,"text":11748},{"id":2857,"depth":97,"text":2858},{"id":2948,"depth":97,"text":2949},"2026-04-12","Entenda zero values, value semantics e pointer semantics em Go: o que são, quando copiar por valor ou passar por ponteiro e como escolher, com exemplos práticos.",{},{"title":18,"description":11937},"5X4sk4fKVmkMi255j5zzegR_u_395FzojlcSobfH-1Q",1786023409214]