proposal: spec: same-scope “everychange” variables #69008
Labels
gabywins
LanguageChange
Suggested changes to the Go language
LanguageChangeReview
Discussed by language change review committee
Proposal
Milestone
Go Programming Experience
Experienced
Other Languages Experience
JS/TS, Go, Kotlin/Java, Python, C, C++, Rust
Related Idea
Has this idea, or one like it, been proposed before?
This proposal introduces the "everychange" keyword. The keyword puts a trap on assignments of a specific variable within the function scope, in a similar way to the "defer" keyword. The main benefit would be to allow better error handling: one could make a code run everytime an
err
variable is set.The closest proposal to this one #67316 suggests adding a trap using the “select” keyword. In my opinion, that is not the right approach.
Using an old keyword “hides” the change and makes the flow difficult to understand. Most notably, it makes errors “special” by assigning them a special behavior and is not backward compatible. In that proposal, errors are treated with a convoluted syntax and there is no easy way to understand when a trap triggers.
Does this affect error handling?
The biggest selling point of this proposal is that it is not a proposal about error handling, but just a proposal which happens to make error handling easier to deal with.
Most other proposals try to make the code more concise by introducing magic and unclear flow. They set default values and behaviors.
This proposal, instead, takes a different approach by being very explicit: no default return, no default behavior, no trying to guess if a function’s last argument is an error. With this proposal everything must be written by the developer.
Is this about generics?
No.
Proposal
Introduction
Many high level languages are implementing ways to watch for variable changes. JavaScript proxies and signals, for example, allow developers to react every time a variable or an object property is set, and compute derived values or execute actions accordingly.
Golang has always strived for being simple, and thus those high level patterns should not be considered for insertion inside the language core, especially since they are so convoluted. Nothing says, however, that Golang needs a full-featured set of keywords and objects to work with reactive variables.
The aim of this proposal is to prove that an extremely simplified way to observe assignment would still be really beneficial, especially since it would provide a clear and concise way to perform error handling. This approach differs from other proposals by not breaking the “errors are values” rule.
Proposal explanation and code example
This proposal introduces only one keyword, “everychange”, which lets you specify a code to run every time a chosen variable is assigned with a value. It's a trap that is invoked every time the variable is assigned inside the same function.
To make obvious that a variable runs something when assigned, those variables should be named in a special way, i.e. by prefixing them with #. Only local variables could be observed (no global variables, no function args). The everychange keyword must be specified in the exact same scope of the observed variable, preferably right after the variable declaration.
For example:
The following code should print "hello\nhello again!" and should be equivalent to:
The real power of everychange comes when performing error handling:
Motivation
Many proposals try to solve error handling by introducing special constructs which are valid for errors and nothing else. This would often result in contradicting the “errors are value” mantra, and provide magical control flows which are hard to debug. This proposal provides a way to handle errors without breaking Golang’s core philosophy.
The extreme simplicity of the keywords and the harsh restrictions makes it really difficult to abuse it or to introduce an hidden control flow: developers would learn very quickly that when they see an assignment of a prefixed #variable, they should check the variable declaration. Forcing everything to be in the same scope would help debugging.
Some specs
The “everychange” keyword must have the exact same scope as the variable. It's forbidden to nest everychange inside an inner
if
or afor
.#
should never be marked as "unused" (this may require further discussion)Language Spec Changes
The two main changes are:
everychange
Informal Change
Thanks to a new keyword,
everychange
, you can now run a code everytime a specific variable is assigned. Could be used only on local variables and those variables should have a name starting with#
.For example:
Is this change backward compatible?
Yes.
Orthogonality: How does this change interact or overlap with existing features?
One of the goal of this proposal is to overlap as less as possible with existing features.
Would this change make Go easier or harder to learn, and why?
Would obviously introduce a new feature to learn; however, the proposal is made in order to be self explanatory and with no magic behind - it should be easier to grasp when compared with most other error handling proposals.
Cost Description
Runtime: this proposal introduces no runtime cost. Everything can be solved during compile time.
Compilation time: this proposal should not add a relevant burden on the compiler. “Everychange variables” lives and dies within a function; even a trivial implementation where the compiler makes a list of triggers and checks the whole list on every assignment within the same function should be very fast.
Code reading: good functions should not be extremely big in the first place, and thus programmers should spot very easily the presence of everychange. Forcing “everychange variables” to have a different naming convention makes sure that it's immediately clear which assignment may trigger a code. The name convention also makes sure to highlight the boundaries of the trigger. The same-scope restriction makes sure the triggers could not be abused.
Overall, one of the main goal of this tutorial is to improve code reading by reducing repeated code.
Language complexity: compared to other proposals, this one introduces just 1 keyword and a clear path on how to handle it. The keyword itself is very powerful and could be used to handle basically every error handling scenario. Other proposals, for example, introduce two different keywords for handling recoverable/unrecoverable errors; with this proposal you can just use two different variables, i.e. #err and #fatalError, and attach them to different everychange clauses.
Implementation: the implementation is not trivial, as it will require several changes to the compiler and pretty much every go tool.
Changes to Go ToolChain
Pretty much every go tool would need an update to handle the new keyword.
Performance Costs
No run time cost; negligible compile time cost.
Prototype
No response
The text was updated successfully, but these errors were encountered: