<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xml:lang="en-us" xmlns:atom="http://www.w3.org/2005/Atom"><channel><copyright>© Will Faught</copyright><language>en-us</language><lastBuildDate>Mon, 19 Oct 2026 12:01:00 -0800</lastBuildDate><link>https://willfaught.com/tags/handling/</link><atom:link href="https://willfaught.com/tags/handling/rss.xml" hreflang="en-us" rel="self" type="application/rss+xml"/><atom:link href="https://willfaught.com/tags/handling/atom.xml" hreflang="en-us" rel="alternate" type="application/atom+xml"/><atom:link href="https://willfaught.com/tags/handling/" hreflang="en-us" rel="alternate" type="text/html"/><atom:link href="https://willfaught.com/tags/handling/rss.xml" hreflang="en-us" rel="alternate" type="application/rss+xml"/><managingEditor>pushes.kingdom_0e@icloud.com (Will Faught)</managingEditor><title>Handling · Tags · Will Do</title><webMaster>pushes.kingdom_0e@icloud.com (Will Faught)</webMaster><item><author>pushes.kingdom_0e@icloud.com (Will Faught)</author><description><![CDATA[<p>In 2018, <a href=https://github.com/rsc rel=external>Russ Cox</a> of the <a href=https://go.dev rel=external>Go</a> team <a href=https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md rel=external>proposed</a> a new way to handle errors in Go:</p><blockquote><p>One way that Go programs fail to scale well is in the writing of error-checking and error-handling code. In general Go programs have too much code checking errors and not enough code handling them. (This will be illustrated below.) The draft design aims to address this problem by introducing lighter-weight syntax for error checks than the current idiomatic assignment-and-if-statement combination.</p></blockquote><p>That draft did not become part of the language.</p><p>In 2019, <a href=https://github.com/griesemer rel=external>Robert Griesemer</a>, another member of the Go team, <a href=https://github.com/golang/go/issues/32437 rel=external>proposed</a> another new way to handle errors in Go:</p><blockquote><p>We propose a new built-in function called <code>try</code>, designed specifically to eliminate the boilerplate <code>if</code> statements typically associated with error handling in Go. No other language changes are suggested. We advocate using the existing <code>defer</code> statement and standard library functions to help with augmenting or wrapping of errors. This minimal approach addresses most common scenarios while adding very little complexity to the language. The <code>try</code> built-in is easy to explain, straightforward to implement, orthogonal to other language constructs, and fully backward-compatible. It also leaves open a path to extending the mechanism, should we wish to do so in the future.</p></blockquote><p>Griesemer <a href=https://github.com/golang/go/issues/32437#issuecomment-512035919 rel=external>announced</a> that the proposal was declined after community feedback:</p><blockquote><p>As far as technical feedback, this discussion has helpfully identified some important considerations we missed, most notably the implications for adding debugging prints and analyzing code coverage.</p><p>More importantly, we have heard clearly the many people who argued that this proposal was not targeting a worthwhile problem. We still believe that error handling in Go is not perfect and can be meaningfully improved, but it is clear that we as a community need to talk more about what specific aspects of error handling are problems that we should address.</p></blockquote><p>I agree that the problem is real and worth solving. This article proposes call operators that simplify propagation of existing error results and a context scope that supplies context arguments at call sites. Both patterns have counterparts in monadic composition. The aim is to adapt that style of composition to Go&rsquo;s customary context parameter and error result while preserving existing function signatures and the behavior of existing programs.</p><p>The proposed additions are just <code>?</code>, <code>!</code>, and <code>with</code>. These are proposed language features, not features available in Go. The background examples use ordinary Java and Go; the design sections use hypothetical Go syntax. Examples omit imports and unrelated helper definitions. Explicit handling remains available wherever a caller needs to inspect, wrap, or recover from an error.</p><h2 id=background>Background</h2><h3 id=exceptions-in-java>Exceptions in Java</h3><p>In Java, a method can report failure by throwing an exception:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#cf222e>void</span><span style=color:#fff> </span><span style=color:#6639ba>a</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#cf222e>throws</span><span style=color:#fff> </span>Exception<span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>    </span><span style=color:#cf222e>throw</span><span style=color:#fff> </span><span style=color:#cf222e>new</span><span style=color:#fff> </span>Exception<span style=color:#1f2328>(</span><span style=color:#0a3069>&#34;s&#34;</span><span style=color:#1f2328>);</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>A checked exception, such as the <code>Exception</code> above, must be caught or declared in the caller&rsquo;s <code>throws</code> clause. Unchecked exceptions do not have that requirement. A caller that wants to handle the exception can catch it:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#cf222e>try</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>    </span>a<span style=color:#1f2328>();</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span><span style=color:#fff> </span><span style=color:#cf222e>catch</span><span style=color:#fff> </span><span style=color:#1f2328>(</span>Exception<span style=color:#fff> </span>e<span style=color:#1f2328>)</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>    </span><span style=color:#57606a>// Handle the exception</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>A <code>finally</code> block runs when control leaves the <code>try</code> statement, including when an exception is propagated:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#cf222e>try</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>    </span>a<span style=color:#1f2328>();</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span><span style=color:#fff> </span><span style=color:#cf222e>catch</span><span style=color:#fff> </span><span style=color:#1f2328>(</span>Exception<span style=color:#fff> </span>e<span style=color:#1f2328>)</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>    </span><span style=color:#57606a>// Handle the exception</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span><span style=color:#fff> </span><span style=color:#cf222e>finally</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>    </span><span style=color:#57606a>// Clean up</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>If <code>finally</code> throws an exception, that exception replaces one already being propagated from the <code>try</code> or <code>catch</code> block.</p><p>Callers can distinguish between kinds of exceptions:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#cf222e>try</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>    </span>a<span style=color:#1f2328>();</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span><span style=color:#fff> </span><span style=color:#cf222e>catch</span><span style=color:#fff> </span><span style=color:#1f2328>(</span>Exception<span style=color:#fff> </span>e<span style=color:#1f2328>)</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>    </span><span style=color:#cf222e>if</span><span style=color:#fff> </span><span style=color:#1f2328>(</span>e<span style=color:#fff> </span><span style=color:#cf222e>instanceof</span><span style=color:#fff> </span>IOException<span style=color:#1f2328>)</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>        </span><span style=color:#57606a>// Handle an I/O failure</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>    </span><span style=color:#1f2328>}</span><span style=color:#fff> </span><span style=color:#cf222e>else</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>        </span><span style=color:#57606a>// Handle another failure</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>    </span><span style=color:#1f2328>}</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>They can also wrap an exception while preserving its cause:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#cf222e>try</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>    </span>a<span style=color:#1f2328>();</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span><span style=color:#fff> </span><span style=color:#cf222e>catch</span><span style=color:#fff> </span><span style=color:#1f2328>(</span>Exception<span style=color:#fff> </span>e<span style=color:#1f2328>)</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>    </span><span style=color:#cf222e>throw</span><span style=color:#fff> </span><span style=color:#cf222e>new</span><span style=color:#fff> </span>Exception<span style=color:#1f2328>(</span><span style=color:#0a3069>&#34;a failed&#34;</span><span style=color:#1f2328>,</span><span style=color:#fff> </span>e<span style=color:#1f2328>);</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>But a caller that only wants to propagate an exception does not need to catch and rethrow it. It can declare the exception and let propagation happen automatically:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-java data-lang=java><span style=display:flex><span><span style=color:#cf222e>void</span><span style=color:#fff> </span><span style=color:#6639ba>x</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#cf222e>throws</span><span style=color:#fff> </span>Exception<span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>    </span>a<span style=color:#1f2328>();</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>    </span>b<span style=color:#1f2328>();</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>    </span>c<span style=color:#1f2328>();</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>If <code>a</code> throws, <code>b</code> and <code>c</code> are not called. Execution unwinds through applicable <code>finally</code> blocks until a matching handler is reached. The exception can retain its stack trace and cause, so propagation does not inherently lose its origin. Wrapping can still add useful information about what the caller was trying to do.</p><p>The useful distinction is between handling a failure and merely passing it to a caller that can handle it.</p><h3 id=errors-in-go>Errors in Go</h3><p>In Go, a function can report failure by returning an error:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#cf222e>func</span><span style=color:#fff> </span><span style=color:#6639ba>a</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#cf222e>error</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#1f2328>errors</span><span style=color:#1f2328>.</span><span style=color:#6639ba>New</span><span style=color:#1f2328>(</span><span style=color:#0a3069>&#34;s&#34;</span><span style=color:#1f2328>)</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>Callers check the returned value explicitly:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#cf222e>if</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#0550ae>:=</span><span style=color:#fff> </span><span style=color:#6639ba>a</span><span style=color:#1f2328>();</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#0550ae>!=</span><span style=color:#fff> </span><span style=color:#cf222e>nil</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#57606a>// Handle the error</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>Go&rsquo;s <a href=https://go.dev/ref/spec#Defer_statements rel=external><code>defer</code> statement</a> schedules cleanup for when the surrounding function returns or unwinds during a panic:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#cf222e>func</span><span style=color:#fff> </span><span style=color:#6639ba>x</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#cf222e>error</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>defer</span><span style=color:#fff> </span><span style=color:#6639ba>cleanup</span><span style=color:#1f2328>()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#6639ba>a</span><span style=color:#1f2328>()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>The deferred call runs before <code>x</code> returns to its caller. A deferred closure can also inspect and modify a named result, including an error result.</p><p>Callers can distinguish between kinds of errors. For example, <a href=https://pkg.go.dev/errors#As rel=external><code>errors.As</code></a> can find a <code>*os.PathError</code>, including one inside a wrapped error:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#cf222e>if</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#0550ae>:=</span><span style=color:#fff> </span><span style=color:#6639ba>a</span><span style=color:#1f2328>();</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#0550ae>!=</span><span style=color:#fff> </span><span style=color:#cf222e>nil</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>var</span><span style=color:#fff> </span><span style=color:#1f2328>pathErr</span><span style=color:#fff> </span><span style=color:#0550ae>*</span><span style=color:#1f2328>os</span><span style=color:#1f2328>.</span><span style=color:#1f2328>PathError</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>if</span><span style=color:#fff> </span><span style=color:#1f2328>errors</span><span style=color:#1f2328>.</span><span style=color:#6639ba>As</span><span style=color:#1f2328>(</span><span style=color:#1f2328>err</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#0550ae>&amp;</span><span style=color:#1f2328>pathErr</span><span style=color:#1f2328>)</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>		</span><span style=color:#1f2328>fmt</span><span style=color:#1f2328>.</span><span style=color:#6639ba>Println</span><span style=color:#1f2328>(</span><span style=color:#1f2328>pathErr</span><span style=color:#1f2328>.</span><span style=color:#1f2328>Path</span><span style=color:#1f2328>)</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>}</span><span style=color:#fff> </span><span style=color:#cf222e>else</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>		</span><span style=color:#57606a>// Handle another failure</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>}</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>Callers can wrap errors while preserving the original error for inspection:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#cf222e>if</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#0550ae>:=</span><span style=color:#fff> </span><span style=color:#6639ba>a</span><span style=color:#1f2328>();</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#0550ae>!=</span><span style=color:#fff> </span><span style=color:#cf222e>nil</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#1f2328>fmt</span><span style=color:#1f2328>.</span><span style=color:#6639ba>Errorf</span><span style=color:#1f2328>(</span><span style=color:#0a3069>&#34;a: %w&#34;</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#1f2328>)</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>Sometimes, a caller cannot usefully handle an error itself, so it returns it unchanged:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#cf222e>func</span><span style=color:#fff> </span><span style=color:#6639ba>x</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#cf222e>error</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>if</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#0550ae>:=</span><span style=color:#fff> </span><span style=color:#6639ba>a</span><span style=color:#1f2328>();</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#0550ae>!=</span><span style=color:#fff> </span><span style=color:#cf222e>nil</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>		</span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>}</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>if</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#0550ae>:=</span><span style=color:#fff> </span><span style=color:#6639ba>b</span><span style=color:#1f2328>();</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#0550ae>!=</span><span style=color:#fff> </span><span style=color:#cf222e>nil</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>		</span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>}</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>if</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#0550ae>:=</span><span style=color:#fff> </span><span style=color:#6639ba>c</span><span style=color:#1f2328>();</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#0550ae>!=</span><span style=color:#fff> </span><span style=color:#cf222e>nil</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>		</span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>}</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#cf222e>nil</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>Many functions, especially those that perform lots of I/O, have little to add at these intermediate steps. Repeating the same check and return can be tedious and can obscure the rest of the code.</p><p>Go lets a function declare an error result, but it does not automatically propagate errors from calls within that function. This version is valid Go, but discards all three errors and reports success:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#cf222e>func</span><span style=color:#fff> </span><span style=color:#6639ba>x</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#cf222e>error</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#6639ba>a</span><span style=color:#1f2328>()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#6639ba>b</span><span style=color:#1f2328>()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#6639ba>c</span><span style=color:#1f2328>()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#cf222e>nil</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>Go already has the tools to inspect, wrap, propagate, and clean up after errors. What it lacks is a concise way to request the common default: return immediately if a call fails, otherwise continue with its other results.</p><h2 id=proposed-call-operators>Proposed call operators</h2><p>The design keeps explicit error results and adds two operators at the call site: <code>?</code> to propagate an error and <code>!</code> to panic on an error.</p><h3 id=default-error-handling>Default error handling</h3><p>A call with <code>?</code> is a <strong>try call</strong>:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#cf222e>func</span><span style=color:#fff> </span><span style=color:#6639ba>x</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#cf222e>error</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>a</span><span style=color:#f6f8fa;background-color:#82071e>?</span><span style=color:#1f2328>()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>b</span><span style=color:#f6f8fa;background-color:#82071e>?</span><span style=color:#1f2328>()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>c</span><span style=color:#f6f8fa;background-color:#82071e>?</span><span style=color:#1f2328>()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#cf222e>nil</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>The caller and callee must each have a final result of type <code>error</code>. The operator handles the callee&rsquo;s error result and removes it from the results available to the surrounding expression. It does not change the callee&rsquo;s function type.</p><p>For example:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#cf222e>func</span><span style=color:#fff> </span><span style=color:#6639ba>example</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#1f2328>(</span><span style=color:#cf222e>int</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#cf222e>error</span><span style=color:#1f2328>)</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>f</span><span style=color:#fff> </span><span style=color:#0550ae>:=</span><span style=color:#fff> </span><span style=color:#cf222e>func</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#1f2328>(</span><span style=color:#cf222e>int</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#cf222e>error</span><span style=color:#1f2328>)</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff> </span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#0550ae>42</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#cf222e>nil</span><span style=color:#fff> </span><span style=color:#1f2328>}</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>n</span><span style=color:#fff> </span><span style=color:#0550ae>:=</span><span style=color:#fff> </span><span style=color:#1f2328>f</span><span style=color:#f6f8fa;background-color:#82071e>?</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#57606a>// Produces an int; f still has type func() (int, error)</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>g</span><span style=color:#fff> </span><span style=color:#0550ae>:=</span><span style=color:#fff> </span><span style=color:#cf222e>func</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#cf222e>error</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff> </span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#cf222e>nil</span><span style=color:#fff> </span><span style=color:#1f2328>}</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>g</span><span style=color:#f6f8fa;background-color:#82071e>?</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#57606a>// Produces no values</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#1f2328>n</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#cf222e>nil</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>If the error is nil, execution continues. Otherwise, the enclosing function returns that error immediately, with zero values for its other results. Its deferred functions then execute under the normal rules, including any changes they make to named results.</p><p>For a caller returning <code>(int, error)</code>, the failure case is equivalent to:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#1f2328>n</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#0550ae>:=</span><span style=color:#fff> </span><span style=color:#6639ba>f</span><span style=color:#1f2328>()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#cf222e>if</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#0550ae>!=</span><span style=color:#fff> </span><span style=color:#cf222e>nil</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#0550ae>0</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>The operator supplies the repeated check and return. It does not decide whether the error needs wrapping or whether additional cleanup is necessary.</p><h3 id=assertion-error-handling>Assertion error handling</h3><p>A call with <code>!</code> is an <strong>assert call</strong>:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#cf222e>func</span><span style=color:#fff> </span><span style=color:#6639ba>main</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>a</span><span style=color:#1f2328>!()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>b</span><span style=color:#1f2328>!()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>c</span><span style=color:#1f2328>!()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>The callee must have a final result of type <code>error</code>. On success, that result is removed from the values produced by the call, just as with a try call. On failure, the caller panics with the error. The caller does not need an error result of its own.</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#cf222e>func</span><span style=color:#fff> </span><span style=color:#6639ba>example</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#cf222e>int</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>f</span><span style=color:#fff> </span><span style=color:#0550ae>:=</span><span style=color:#fff> </span><span style=color:#cf222e>func</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#1f2328>(</span><span style=color:#cf222e>int</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#cf222e>error</span><span style=color:#1f2328>)</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff> </span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#0550ae>42</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#cf222e>nil</span><span style=color:#fff> </span><span style=color:#1f2328>}</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#1f2328>f</span><span style=color:#1f2328>!()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>This could be useful in <code>main</code>, which cannot return an error, or where a returned error indicates a programmer mistake. It is a deliberate choice to panic, rather than to propagate an ordinary error result. It is unrelated to Java&rsquo;s <code>assert</code> statement.</p><h3 id=control-flow-and-evaluation>Control flow and evaluation</h3><p>Try calls and assert calls have different failure behavior. A try call returns normally from its enclosing function. An assert call starts a panic, which unwinds the current goroutine and runs its deferred calls. An appropriate deferred <code>recover</code> can stop that panic; otherwise the unrecovered panic terminates the program. It does not turn into an ordinary error return at the caller&rsquo;s call site.</p><p>Both operators would preserve Go&rsquo;s evaluation-order rules. If an inner call fails, the parts of the enclosing expression that have not yet been evaluated are skipped. For example, assuming suitable parameter and result types:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#1f2328>result</span><span style=color:#fff> </span><span style=color:#0550ae>:=</span><span style=color:#fff> </span><span style=color:#1f2328>combine</span><span style=color:#f6f8fa;background-color:#82071e>?</span><span style=color:#1f2328>(</span><span style=color:#1f2328>left</span><span style=color:#f6f8fa;background-color:#82071e>?</span><span style=color:#1f2328>(),</span><span style=color:#fff> </span><span style=color:#1f2328>right</span><span style=color:#f6f8fa;background-color:#82071e>?</span><span style=color:#1f2328>())</span></span></span></code></pre></div><p>If <code>left</code> returns an error, neither <code>right</code> nor <code>combine</code> is called.</p><p>Try calls can appear in function literals when the literal has the required final error result. They return from that literal, not from the function that created it.</p><p>A direct <code>go f?()</code> would not be permitted: a failure in the new goroutine cannot return an error from the function that launched it. A direct <code>go f!()</code> would panic in the new goroutine if <code>f</code> failed. Calls used to evaluate arguments to a <code>go</code> statement still execute in the launching goroutine under Go&rsquo;s ordinary rules.</p><h3 id=deferred-calls>Deferred calls</h3><p>A deferred try call would check the error when the deferred function executes. If the enclosing function already has a non-nil error result, the deferred call&rsquo;s error is ignored: it might be a consequence of the failure already being reported. If the pending error result is nil, a non-nil cleanup error becomes the error result. Other result values are left unchanged.</p><p>For example:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#cf222e>func</span><span style=color:#fff> </span><span style=color:#6639ba>run</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#cf222e>error</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>defer</span><span style=color:#fff> </span><span style=color:#1f2328>cleanup</span><span style=color:#f6f8fa;background-color:#82071e>?</span><span style=color:#1f2328>()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#6639ba>work</span><span style=color:#1f2328>()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>The error-handling behavior would be equivalent to this ordinary Go code, where the error result is named so that the deferred closure can update it:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#cf222e>func</span><span style=color:#fff> </span><span style=color:#6639ba>run</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#1f2328>(</span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#cf222e>error</span><span style=color:#1f2328>)</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>defer</span><span style=color:#fff> </span><span style=color:#cf222e>func</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>		</span><span style=color:#cf222e>if</span><span style=color:#fff> </span><span style=color:#1f2328>cleanupErr</span><span style=color:#fff> </span><span style=color:#0550ae>:=</span><span style=color:#fff> </span><span style=color:#6639ba>cleanup</span><span style=color:#1f2328>();</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#0550ae>==</span><span style=color:#fff> </span><span style=color:#cf222e>nil</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>			</span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#1f2328>=</span><span style=color:#fff> </span><span style=color:#1f2328>cleanupErr</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>		</span><span style=color:#1f2328>}</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>}()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#6639ba>work</span><span style=color:#1f2328>()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>The source function does not need to name its error result. The deferred <code>?</code> call gives the compiler access to the pending result, just as naming it does in the expansion above; the function type remains <code>func() error</code>.</p><p>Deferred calls still run in reverse registration order. Each deferred try call preserves a non-nil error already selected by the body or a previously executed deferred call. The check does not recover a panic; panic and recovery retain their ordinary behavior.</p><p>A deferred assert call would instead panic on a non-nil error when the deferred function executes, as explicitly requested by <code>!</code>.</p><h3 id=trade-offs>Trade-offs</h3><p>A try call is appropriate when returning an error unchanged is enough. When the caller needs to identify the failed operation or take some other action, explicit handling remains useful:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#cf222e>func</span><span style=color:#fff> </span><span style=color:#6639ba>x</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#cf222e>error</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>a</span><span style=color:#f6f8fa;background-color:#82071e>?</span><span style=color:#1f2328>()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>if</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#0550ae>:=</span><span style=color:#fff> </span><span style=color:#6639ba>b</span><span style=color:#1f2328>();</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#fff> </span><span style=color:#0550ae>!=</span><span style=color:#fff> </span><span style=color:#cf222e>nil</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>		</span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#1f2328>fmt</span><span style=color:#1f2328>.</span><span style=color:#6639ba>Errorf</span><span style=color:#1f2328>(</span><span style=color:#0a3069>&#34;b: %w&#34;</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#1f2328>err</span><span style=color:#1f2328>)</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>}</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>c</span><span style=color:#f6f8fa;background-color:#82071e>?</span><span style=color:#1f2328>()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#cf222e>nil</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>An error value might already identify its origin, or a library might attach a stack trace. The proposed operators would not add that information automatically. This differs from Java exceptions, which normally capture a stack trace when created.</p><p>Debugging remains possible with tests, logging, breakpoints, and explicit checks. A debugger could also support stopping at a failed try call, but that would require tooling support; it is not a consequence of the syntax alone.</p><p>The operators make possible exits less prominent than a full <code>return</code> statement. That is a real trade-off. The question is whether a visible marker at each call site provides enough clarity to justify removing the repeated check-and-return blocks.</p><p>These operators introduce syntax without reserving new keywords or changing existing function types. Existing calls retain their behavior; propagation or assertion happens only where a programmer adds an operator. That is the compatibility boundary of this design. A complete proposal would still need to specify the grammar and type-checking rules in detail.</p><h2 id=context-propagation>Context propagation</h2><p>Errors travel up a call chain. Contexts create a similar pattern in the other direction: a <a href=https://pkg.go.dev/context rel=external><code>context.Context</code></a> carries deadlines, cancellation signals, and request-scoped values down through calls. Intermediate functions often do little with it beyond passing it along.</p><p>Request-oriented APIs commonly have signatures like this:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#cf222e>func</span><span style=color:#fff> </span><span style=color:#6639ba>work</span><span style=color:#1f2328>(</span><span style=color:#1f2328>context</span><span style=color:#1f2328>.</span><span style=color:#1f2328>Context</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#1f2328>Input</span><span style=color:#1f2328>)</span><span style=color:#fff> </span><span style=color:#1f2328>(</span><span style=color:#1f2328>Output</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#cf222e>error</span><span style=color:#1f2328>)</span></span></span></code></pre></div><p>The context parameter supplies an environment for the work, while the error result reports whether it succeeded. Many intermediate functions only need to pass the context down and pass an error back up. Those are two related kinds of boilerplate.</p><h3 id=a-context-scope>A context scope</h3><p>A proposed predeclared function, <code>with</code>, would establish a context for calls written inside a function literal:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#cf222e>func</span><span style=color:#fff> </span><span style=color:#6639ba>load</span><span style=color:#1f2328>(</span><span style=color:#1f2328>ctx</span><span style=color:#fff> </span><span style=color:#1f2328>context</span><span style=color:#1f2328>.</span><span style=color:#1f2328>Context</span><span style=color:#1f2328>)</span><span style=color:#fff> </span><span style=color:#1f2328>(</span><span style=color:#1f2328>Value</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#cf222e>error</span><span style=color:#1f2328>)</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#6639ba>with</span><span style=color:#1f2328>(</span><span style=color:#1f2328>ctx</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#cf222e>func</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#1f2328>(</span><span style=color:#1f2328>Value</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#cf222e>error</span><span style=color:#1f2328>)</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>		</span><span style=color:#1f2328>data</span><span style=color:#fff> </span><span style=color:#0550ae>:=</span><span style=color:#fff> </span><span style=color:#1f2328>read</span><span style=color:#f6f8fa;background-color:#82071e>?</span><span style=color:#1f2328>()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>		</span><span style=color:#cf222e>return</span><span style=color:#fff> </span><span style=color:#1f2328>parse</span><span style=color:#f6f8fa;background-color:#82071e>?</span><span style=color:#1f2328>(</span><span style=color:#1f2328>data</span><span style=color:#1f2328>),</span><span style=color:#fff> </span><span style=color:#cf222e>nil</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>})</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>Here, <code>read</code> still has an explicit first <code>context.Context</code> parameter in its declaration. The compiler supplies <code>ctx</code> at the call site, as if the source said <code>read?(ctx)</code>. If <code>parse</code> has no context parameter, its call is unchanged. Both calls separately opt into error propagation with <code>?</code>.</p><p>The function literal can also reference <code>ctx</code> directly through ordinary lexical scope if it needs to inspect cancellation, derive another context, or otherwise use the context value. No context getter is needed.</p><p><code>with</code> would evaluate its context expression once, invoke the parameterless function literal once, and produce that invocation&rsquo;s ordinary results. A try call inside the literal returns from the literal; the outer <code>return with(...)</code> passes those results back to <code>load</code>&rsquo;s caller.</p><p>Like <code>append</code> or <code>new</code>, the name <code>with</code> could be rebound in an inner scope. The context-passing behavior would apply only to calls that resolve to the predeclared function; calls to a user-defined <code>with</code> would follow ordinary call rules.</p><p>The predeclared function needs compiler support. An ordinary library function cannot make a call with a missing argument type-check. For this design, the second argument is a function literal whose call expressions the compiler can inspect, rather than an arbitrary function value whose body might be elsewhere.</p><h3 id=scope-and-explicit-arguments>Scope and explicit arguments</h3><p>The scope is lexical: it covers calls written in the literal&rsquo;s body, including nested function literals. It does not alter the bodies of separately defined callees. Those functions still receive their declared parameters and choose how to pass them onward.</p><p>A call that is already valid with its written arguments keeps its ordinary meaning. Otherwise, if its first parameter is <code>context.Context</code> and inserting the scope&rsquo;s context as the first argument makes the call valid, the compiler supplies that argument. This gives explicitly supplied contexts precedence and leaves calls without context parameters alone. Outside a <code>with</code> scope, all arguments remain explicit.</p><p>Nested scopes can select a different context. For example, assuming <code>notify</code> is declared as <code>func notify(context.Context, string)</code>:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#6639ba>with</span><span style=color:#1f2328>(</span><span style=color:#1f2328>ctx</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#cf222e>func</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#6639ba>notify</span><span style=color:#1f2328>(</span><span style=color:#0a3069>&#34;first&#34;</span><span style=color:#1f2328>)</span><span style=color:#fff>            </span><span style=color:#57606a>// Receives ctx</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#6639ba>notify</span><span style=color:#1f2328>(</span><span style=color:#1f2328>other</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#0a3069>&#34;explicit&#34;</span><span style=color:#1f2328>)</span><span style=color:#fff>  </span><span style=color:#57606a>// Receives the explicit other context</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#6639ba>with</span><span style=color:#1f2328>(</span><span style=color:#1f2328>other</span><span style=color:#1f2328>,</span><span style=color:#fff> </span><span style=color:#cf222e>func</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>		</span><span style=color:#6639ba>notify</span><span style=color:#1f2328>(</span><span style=color:#0a3069>&#34;second&#34;</span><span style=color:#1f2328>)</span><span style=color:#fff>       </span><span style=color:#57606a>// Receives other</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>})</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#6639ba>notify</span><span style=color:#1f2328>(</span><span style=color:#0a3069>&#34;third&#34;</span><span style=color:#1f2328>)</span><span style=color:#fff>            </span><span style=color:#57606a>// Receives ctx again</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>})</span></span></span></code></pre></div><p>The selected context behaves like an ordinary captured value, not mutable goroutine-wide state. The scope does not require an extra parameter or result in any function&rsquo;s type. Context arguments supplied to deferred calls and <code>go</code> statements are evaluated and saved under Go&rsquo;s ordinary rules, just as if they had been written explicitly.</p><p>At an entry point such as <code>main</code> or <code>init</code>, code can establish <code>context.Background()</code> as the scope&rsquo;s context and use assert calls to panic on returned errors:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#cf222e>func</span><span style=color:#fff> </span><span style=color:#6639ba>main</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#6639ba>with</span><span style=color:#1f2328>(</span><span style=color:#1f2328>context</span><span style=color:#1f2328>.</span><span style=color:#6639ba>Background</span><span style=color:#1f2328>(),</span><span style=color:#fff> </span><span style=color:#cf222e>func</span><span style=color:#1f2328>()</span><span style=color:#fff> </span><span style=color:#1f2328>{</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>		</span><span style=color:#1f2328>run</span><span style=color:#1f2328>!()</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#fff>	</span><span style=color:#1f2328>})</span><span style=color:#fff>
</span></span></span><span style=display:flex><span><span style=color:#1f2328>}</span></span></span></code></pre></div><p>These are explicit choices at the entry point, rather than new behavior imposed on existing programs. Passing a context still does not itself stop work when the context is canceled. The functions doing the work must observe cancellation, either directly or through the APIs they call.</p><h2 id=monadic-composition>Monadic composition</h2><p>Go&rsquo;s error results and context parameters are manual versions of two patterns that other languages make easier to compose. An error result resembles a computation that can fail; a context parameter resembles a computation that depends on a shared environment. For routine propagation, I see Go&rsquo;s conventions as worse versions of those abstractions: they encode much of the same structure but leave programmers to write the sequencing rules repeatedly.</p><h3 id=failure-as-a-composable-result>Failure as a composable result</h3><p>A monad provides operations for embedding an ordinary value in a computation and composing computations while preserving their structure. For error handling, the useful composition rule is simple: on success, pass the value to the next computation; on failure, carry the error forward without running that next computation.</p><p>Haskell&rsquo;s <a href=https://hackage.haskell.org/package/base/docs/Data-Either.html rel=external><code>Either e a</code></a> represents either an error in <code>Left</code> or a successful result in <code>Right</code>. Its <code>Monad</code> instance supplies that sequencing rule through bind, written <code>>>=</code>, and <code>do</code> notation provides a convenient syntax for using it. The error checks belong to the shared composition operation rather than to every caller.</p><p>Rust&rsquo;s <a href=https://doc.rust-lang.org/std/result/enum.Result.html rel=external><code>Result&lt;T, E></code></a> has a similar success-or-failure structure, with <code>and_then</code> for sequencing successful results. Its <a href=https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-try-propagation-expression rel=external><code>?</code> operator</a> extracts a successful value or returns an error from the enclosing function, with the supported error conversion. Rust does not need Haskell&rsquo;s general <code>Monad</code> type class to provide this particular form of composition. Scala&rsquo;s <a href=https://www.scala-lang.org/api/2.13.16/scala/util/Either.html rel=external><code>Either</code></a> offers another example, with <code>flatMap</code> carrying <code>Left</code> values forward and applying the next function to <code>Right</code> values.</p><p>JavaScript&rsquo;s <a href=https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-promise-objects rel=external><code>Promise</code></a> is commonly understood as a monadic abstraction for asynchronous success and failure: <code>Promise.resolve</code> lifts ordinary values into promises, and <code>.then</code> plays the role of bind. A chain such as:</p><div class=highlight><pre tabindex=0 style=background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-javascript data-lang=javascript><span style=display:flex><span><span style=color:#1f2328>read</span><span style=color:#1f2328>().</span><span style=color:#1f2328>then</span><span style=color:#1f2328>(</span><span style=color:#1f2328>parse</span><span style=color:#1f2328>).</span><span style=color:#1f2328>then</span><span style=color:#1f2328>(</span><span style=color:#1f2328>use</span><span style=color:#1f2328>)</span></span></span></code></pre></div><p>passes each fulfilled value to the next function, adopts a promise returned by that function, and carries rejection past success handlers until a rejection handler is reached. <code>async</code>/<code>await</code> provides another syntax for sequencing promises; a rejected <code>await</code> throws, so it propagates unless handled. Promise resolution also adopts objects with a callable <code>then</code> method—<code>then</code>-able objects. That assimilation and asynchronous scheduling give JavaScript promises their own semantics, so the comparison is about their monadic sequencing rather than a claim that they obey the monad laws for every possible JavaScript value.</p><p>Go&rsquo;s <code>(T, error)</code> convention lacks a standard result type with comparable composition operations. It supplies two separate values, and callers repeat the success test and propagation themselves. It also differs from <code>Either</code> and <code>Result</code>: Go APIs can return useful partial results alongside a non-nil error. The proposed try call deliberately discards the callee&rsquo;s other results on failure, so code that needs those partial results should keep an explicit check.</p><p>A Go library could provide a result wrapper and composition helpers, but existing APIs would still return separate values. Using the library would require adapters, wrapping and unwrapping values, and often additional callbacks or combinators. That adds the kind of verbosity this design is meant to remove. The operators instead apply the composition rule directly to existing APIs and their established error convention.</p><h3 id=a-shared-environment>A shared environment</h3><p>Haskell&rsquo;s <a href=https://hackage.haskell.org/package/transformers/docs/Control-Monad-Trans-Reader.html rel=external><code>Reader r a</code></a> represents a computation that uses an environment of type <code>r</code> to produce an <code>a</code>. Its bind operation passes the same environment to the composed computations.</p><p>That is close to what Go programmers do by passing a <code>context.Context</code> through a call chain. The dependency is present in both designs. The ergonomic difference is that Reader&rsquo;s composition operation threads it through automatically, whereas ordinary Go callers must repeat the argument at each relevant call. A <code>with</code> scope supplies the corresponding shortcut for calls in an explicitly selected region of Go code.</p><p>The patterns can be combined. A Haskell computation with type <code>ReaderT Context (Either Error) Value</code> receives an environment and can fail; running it supplies a <code>Context</code> and produces an <code>Either Error Value</code>. If it also needs I/O, a type such as <code>ReaderT Context (ExceptT Error IO) Value</code> combines the environment, failure, and I/O effects. <a href=https://hackage.haskell.org/package/transformers/docs/Control-Monad-Trans-Except.html rel=external><code>ExceptT</code></a> supplies the error layer.</p><p>This explains why Go&rsquo;s common first-context-parameter, last-error-result signature is more than an arbitrary convention. It combines an environment going in with possible failure coming out, but without a shared abstraction for composing the two. Reader-style environment passing alone does not implement cancellation, just as promise rejection alone does not cancel asynchronous work; those require cooperation from the operations being composed.</p><h3 id=adapting-composition-to-go>Adapting composition to Go</h3><p>The proposed syntax adapts both patterns to Go&rsquo;s existing design. A try call supplies the success-or-propagate rule at a marked call site, using the existing final <code>error</code> result. An assert call supplies the alternative policy of panicking on failure. A <code>with</code> scope supplies a shared context argument to calls that need one.</p><p>The context parameter and error result remain explicit in function declarations. The shortcuts operate where the programmer requests them, without adding hidden parameters, changing function types, or requiring adapters around existing APIs. Outside those explicitly selected call sites and scopes, existing programs retain their behavior.</p>]]></description><guid isPermaLink="false">tag:willfaught.com,2022-08-17:/2022/08/17/monadic-error-and-context-handling-for-go/</guid><link>https://willfaught.com/2022/08/17/monadic-error-and-context-handling-for-go/</link><atom:link href="https://willfaught.com/2022/08/17/monadic-error-and-context-handling-for-go/" hreflang="en-us" rel="alternate" type="text/html"/><pubDate>Wed, 17 Aug 2022 16:49:07 -0700</pubDate><title>Monadic Error And Context Handling For Go</title></item></channel></rss>