-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: Go 2: error handling: try statement with handler #56165
Comments
as per golang#56165 This was automated with a Semgrep rule and sed. It only demonstrates the form of try without an error handler. Example of the form with an error handler will need to be done manually.
Thanks for writing up this refined proposal, @gregwebs! Although I have personally become accustomed to the "long form" of error handling in today's Go and don't feel a strong need to reduce it, I do also recognize that many other Go programmers -- including those new to Go -- find the current approach rather jarring/uncomfortable. Of all of the proposals for first-class error-flow-control constructs so far, this one is the one I feel most comfortable with because I can easily follow what it's a shorthand for, without having to develop any complex new mental models once I've learned about the new keyword. Some initial reactions below. Some of these are just clarifications to make sure I understood correctly, while others are actual feedback.
I think the above is the extent of my initial substantive reaction. Everything else is details that I would not expect to affect the acceptance of the proposal. The following is therefore intended as presumptuous feedback for a subsequent effort to develop this from an accepted proposal into a final design, rather than for deciding whether to accept the proposal:
Thanks again! |
@apparentlymart thank you so much, I value your feedback and the time you have taken to properly understand the proposals. And I really appreciate how you have separated it out into 2 sections. I think I will alter the proposal to put the content on
In that case is is possible to do this transformation in the future then? But when I look to the future I hope for a language that has sum types and is using a result type and an option type. And I think this would only need to work for those two types. So attempting to generalize now to zero types may cause problems in the future. As for the details not affecting acceptance:
I actually prefer
What do you think about other possible options to replace a comma:
|
I did some automated language transforms to use try on the golang codebase. This is easily automated now with Semgrep rules and a little shell script. Unfortunately it is only examples of using try without an error handler. Try with an error handler is much more difficult to automate. |
There is no need for end users to define an error handling function. As stated, there are probably about 3 error handling helpers that satisfy normal usage. And it is trivial to define them if you need to. I defined them in my package, but I will reproduce them here. func Fmtw(format string, args ...any) func(error) error {
return func(err error) error {
args = append(args, err)
return fmt.Errorf(format+": %w", args...)
}
}
func Fmt(format string, args ...any) func(error) error {
return func(err error) error {
args = append(args, err)
return fmt.Errorf(format+": %v", args...)
}
}
func Cleanup(handler func()) func(error) error {
return func(err error) error {
handler()
return err
}
} So rather than try err, func(err error) error {
return fmt.Errorf("valAndError %w", err)
} One would write try err, Fmtw("valAndError") I will see if I can better incorporate these into the proposal to make this clear. |
try.Try and try.Check can be used. This just requires an intermediate error variable. In many real world cases this is a good way to make code readable. This aligns the library to community feedback and this proposal: golang/go#56165
Thanks for the clarification on the error handler functions, @gregwebs . On my first read I understood that as something provided by user/library code rather than standard library, but providing some ready to use does indeed improve the convenience of the wrapping case. |
While I do like the idea behind this proposal as it would reduce code boilerplate, I have to ask if there has been any reflections on the usage combined with the current I have a hard time seeing this integrate well with errors.Is and errors.As which I find frequently used to handle certain types of errors. In those cases I suppose that logic would instead be inside on the the Handlers, and thus making the code much harder to read and understand. The usage would be smth like try err, func(err error) error {
If errors.Is(ett,ErrMissingData){
return nil // don't want to err on SQL no row matches etc
}
return fmt.Errorf("valAndError %w", err)
} This increases complexity, especially for junior devs I think. The current err handeling would be If !errors.Is(ett,ErrMissingData){
return fmt.Errorf("valAndError %w", err)
}
|
@percybolmer filtering errors is something I have thought about but didn't mention in the proposal, maybe it deserves an appendix section. In my own experience, filtering comes about most often in the use case you are presenting: a lookup in which no data is returned. In my view, many of these APIs could be designed to return But let's discuss how to handle this if the Today you would write: if err != nil && !errors.Is(err, ErrMissingData) {
return fmt.Errorf("valAndError %w", err)
} With // This function would exist on the errors package
func FilterIs(err, target error) error {
if errors.Is(err, target) {
return err
}
return nil
}
try errors.FilterIs(err, ErrMissingData), fmt.Handlew("valAndError") But I think I might prefer the original or something quite similar if !errors.Is(err, ErrMissingData) {
try err, fmt.Handlew("valAndError")
} Although to reiterate my original point, I feel that this example is contrived: we should probably do something different for |
There are 2 boilerplate reductions from this proposal:
I believe the latter is well addressed by this proposal that automatically generates zero values from |
I do see what you mean, and I am fond of your solution, but also afraid of it. Zero values can already be reduced by using named returns, so I don't see a need for that tbh. However named returns do not seem to be very popular in code bases. So maybe an alternative as suggested could prove handy. Also, there is short hand if statements which makes them one liners. Having helper methods as suggested increases complexity though, it might seem trivial, but many developer even find errors.Is and errors.As confusing. Sure they might be juniors, but the current beauty of Go is that it is easy for even juniors, and this would be a step that increases the complexity for a small boilerplate reduction, or even an increase in having handlers assigned anonymously or as accessible functions. |
This was pointed out in the
I appreciate bringing this perspective. I would say though that |
If this is an essence of the past error proposals, you could make it easier for the occasional reader of go code, if you borrow the "return if" naming from proposals like #53017. The
This would better indicate that this is a return statement, and still allow to grep for "return" through the source code. If you would addionally allow to infer the '!= nil' comparision for values of type error in if statements, the operation could be made even more obvious. Then the
|
This comment was marked as off-topic.
This comment was marked as off-topic.
There is no try only do. Go errors are explicit, verbose and on the nose. If you hide it behind magic, you will have a bad time. If I read the code, I can clearly identify an error and what will happen if said error does occur. I don't want to hide it! I want to see, read and be reminded of its existence. Otherwise, I might miss an important error. Also, I'm adding different error messages for each error with |
@Skarlso I agree and I think that's the idea behind the "lazy" appendix, which I think would be essential to make this useful. try err, fmt.Errorf("f fail: %w", err) |
@dmajkic that seems like a materially different proposal than what this issue is representing -- the only similarity is the keyword |
I strongly believe we should not add the Some other things that stick out to me:
|
I disagree that this proposal makes anything more "magical" or "hides" anything. This proposal provides syntactic sugar to handle an extremely common usecase and the final result is clearer more succinct code. I am personally okay with the current It must be noted that this proposal maintains the current spirit of Go's error handling- the errors remain explicit and one is free to drop into |
Will we be able to do |
This proposal is rather inconsistent in the definition of try's grammar. Additionally, in my opinion, this proposal is much more costly, that the author suggest. It's not only the keyword to learn, but also a difference of when to use |
> Will we be able to do I think this perfectly exemplifies the concern raised by @steeling about the try keyword being misleading. On the topic of the keyword, other options presented also produce misled interpretations. For example: in a lot of ways, I don't disagree at all with the idea of making it more readable. I apologize for bringing in an alternative to the discussion; I think some contrast might accentuate my point about how better syntax can alleviate the natural and unavoidable consequence of introducing syntax concepts that seem extensible.
It looks a bit too English-like for what the rest of the language usually does. Still, it delivers simplicity and ease of reading, and it is specific enough that fewer attempts to expand the syntax would be prompted. The language sounds specific to error handling, which confines the scope of this language change. The internals of the implementation on this proposal would work perfectly fine with this, and additionally, you would circumvent the issue of using a comma. |
I just want to clear up some misconceptions
No. If you normally use fmt.Errorf, you just need the same function in curried form written once somewhere in a lib or your code. If we use the lazy approach then you would just use fmt.Errorf as usual.
Yes, this is mentioned explicitly in the proposal
No, this proposal leaves assignment to assignment and error handling to error handling |
What does the following return? func Example() (string, error, error) {
err := doSomething()
if err { return err }
} |
Equivalent to: return "", nil, err |
A few people now (across this and some earlier proposals) have proposed alternatives which seem to involve combining the following two independent building-blocks:
I think it's instructive that of all of these at the time of writing only the second set remains open, and in particular the first set related to using I think the shortest possible alternative using the above ideas, without using any mechanisms that have already been declined in previous proposals, would be something like: if err != nil {
return ..., err
} or indeed, the one-liner form of that: if err != nil { return ..., err } ...but that's already essentially what #21182 proposes -- the above uses nothing except a form of that proposal -- and so that other issue is probably the better place to discuss the pros and cons of that approach, rather than here. |
what if we let a function that returns an error value register an handler and have a way to tell it to either handle any error with the error handler it registered or explicitly gets handled by the caller |
I think the error handling should be pre-defined in the same function with your error try. catch (v any, err error) bool {
if err != nil {
return true
}
return false
} Or like this:
Or some function your defined
Or not defined, it will call the default
the return value must bool to tell go return Then below the same function, you can do like this: try err := doSomething()
//or
try err Or try r, err := os.Open(src)
//or
try r,err The number of try parameters must be the same as the number of catch parameters. If only error is checked, the preceding parameters can be defined as any catch (err error) bool {
if err != nil {
return true
}
return false
} This is to be checked separately if there are multiple parameters try err := doSomething()
//or
try err Further, if the handling of anonymous functions can be handled well. This is also possible.
All catches can actually be considered as anonymous functions. This will greatly streamline the code, and in many cases one line will suffice.
|
Interesting concept but I feel it is just adding another layer of interpretation and possible confusion to what is in reality a very straight-forward part of Go. All to save 1-2 lines of code, feels more about laziness than readability. |
I have a idea, error just a value,no magic. func Bar1() (int, error)
func Far() error {
err! error {
if err != nil {
return err
}
}
b1, err! := Bar1()
_, err! = Bar1()
/* after translated
var _err0 error
b1, _err0 := Bar1()
{
var err error = _err0
if err != nil {
return err
}
}
var _err1 error
_, _err1 = Bar1()
{
var err error = _err0
if err != nil {
return err
}
}
*/
_=b1
return nil
} // not just for error, also for bool
ok! bool {
if !ok {
return errors.New("not found")
}
}
var m map[int]int
a, ok! := m[5]
s += a
a, ok! = m[3]
s += a // may no return
var ls []int
item! int {
if i % 2 == 0 {
ls = append(ls, item)
}
}
item! = 1
item! = 2
item! = 3
item! = 4
item! = 5
// ls == []int{2,4} |
It doesn't seem there is any new discussion on this proposal (instead just different proposals). The different proposals being posted here have all been proposed before. Let me try to summarize the feedback on this proposal. If someone wants to chime in with major points that I missed, that would be appreciated. Otherwise I would be happy to close the discussion now. The below are points that in theory are easy to resolve by altering the existing proposal:
Below are points that were raised about the benefits and costs of this proposal:
And of course, there are a lot of value judgements about whether adding this new feature is worth the cost or otherwise the right thing to do for Golang. In the above I tried to write down any factual statements that came out of these, but sometimes they are hard to tease out. |
@ianlancetaylor as a side note I think the model of using an issue as a proposal is broken. Proposing via pull request (as is commonly done in community RFC processes) would be a big improvement because it would allow for and encourage a clear discussion thread on a particular point of a proposal. |
I wonder if the proposal here, and what you describe are mutually exclusive. I agree having type Optional[T] struct {
...
}
func (o Optional[T]) Unwrap() error {...}
optional := myFunc()
check optional.Unwrap() This proposal would bring us closer to being able to do that, as opposed to an Unwrap, with |
@gregwebs I agree that GitHub issues are far from perfect. But the discussion aspect of a proposal is essential, and at least on GitHub discussions on a pull request are no better than discussions on issues. (Note that we have recently started using the relatively new GitHub discussions for some topics. They are also imperfect but are slightly better than GitHub issues.) |
IMHO Pull request discussions are better because they organize threads on a
particular point. It may look fairly similar from the point of view of
email updates, but there’s a big difference in the ability of those new to
the discussion to take it in or now to look back and understand the
discussion that took place
…On Mon, Oct 24, 2022 at 4:19 PM Ian Lance Taylor ***@***.***> wrote:
@gregwebs <https://github.com/gregwebs> I agree that GitHub issues are
far from perfect. But the discussion aspect of a proposal is essential, and
at least on GitHub discussions on a pull request are no better than
discussions on issues.
(Note that we have recently started using the relatively new GitHub
discussions for some topics. They are also imperfect but are slightly
better than GitHub issues.)
—
Reply to this email directly, view it on GitHub
<#56165 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAAJH4V6PL3FUR5WG63AJ3WE34PVANCNFSM6AAAAAARCYKINE>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
I see no advantage to that feature over what GitHub discussions provides. (That said, this is not the place for this discussion thread. golang-nuts would be better. Thanks.) |
GitHub issues are clear and unambiguous. |
I believe we put all the language changes for error handling on hold. We should probably do the same for this one. |
Thanks for the proposal. I am fully against it:
But, above all, it steepens significantly the learning curve: Learning case one: erros are values, errors are there when a non nil value is detected Learning case two: a special keyword, try, is introduced, that behaves in the following way: case 1... case 2... case 3, and code is autogenerated... Basically it boils down to targeting people coming from Java/Javascript, where it's easier, in the beginning, to read code that has "try", so we obfuscate a clean language to make the learning curve shorter by one day. |
I will mark this proposal as closed since there has been nothing new added to the discussion in quite some time. I would support locking discussion on it, but as a reminder, there is an Unsubscribe button on this page. I will put my summary in the proposal itself. |
Thanks for the proposal. |
I like isEven, err := even(i)? expands to isEven, err := even(i)
if err != nil {
return makeGenericWithDefault[bool](), err
} I wrote a dumb script for my own use: package main
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
)
type FuncInfo struct {
Name string
ReturnType string
}
var funcMap map[string]FuncInfo
func main() {
// Check if there is at least one command-line argument
if len(os.Args) < 2 {
fmt.Println("Please provide a directory name")
os.Exit(1)
}
// Get the last argument as the directory name
dirName := os.Args[len(os.Args)-1]
// Walk through the directory and its subdirectories
err := filepath.Walk(dirName, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Check if the file has the .rgo extension
if filepath.Ext(path) == ".rgo" {
// Read the file content
content, err := os.ReadFile(path)
if err != nil {
fmt.Println("Error reading file:", err)
return err
}
code := string(content)
funcMap = make(map[string]FuncInfo)
findFunctions(code)
code = replaceQuestionMarks(code)
code += "\n" + strings.Trim(`func makeGenericWithDefault[T any]() T {
var t T
return t
`, "\n\r\t ") + "\n}"
// Write the modified code to a new file with the .go extension
err = os.WriteFile(path[:len(path)-4]+".go", []byte(code), 0644)
if err != nil {
fmt.Println("Error writing file:", err)
return err
}
}
return nil
})
if err != nil {
fmt.Println("Error walking directory:", err)
os.Exit(1)
}
}
func findFunctions(code string) {
funcRegex := regexp.MustCompile(`func (\w+)\((.*?)\) \((.*?)\)`)
matches := funcRegex.FindAllStringSubmatch(code, -1)
for _, match := range matches {
funcName := match[1]
returnType := match[3]
funcInfo := FuncInfo{
Name: funcName,
ReturnType: returnType,
}
funcMap[funcName] = funcInfo
}
}
func replaceQuestionMarks(code string) string {
callRegex := regexp.MustCompile(`(\w+), err := (\w+)\((.*?)\)\?`)
matches := callRegex.FindAllStringSubmatchIndex(code, -1)
for i := len(matches) - 1; i >= 0; i-- {
match := matches[i]
start := match[0]
end := match[1]
funcName := code[match[4]:match[5]]
funcInfo, ok := funcMap[funcName]
if !ok {
fmt.Println("Error: function not found:", funcName)
os.Exit(1)
}
returnType := funcInfo.ReturnType
if returnType == "" {
fmt.Println("Error: function has no return type:", funcName)
os.Exit(1)
}
returnTypes := strings.Split(returnType, ",")
for i, t := range returnTypes {
returnTypes[i] = strings.TrimSpace(t)
}
defaultValues := make([]string, len(returnTypes))
for i, t := range returnTypes {
switch t {
case "error":
defaultValues[i] = "err"
default:
defaultValues[i] = fmt.Sprintf("makeGenericWithDefault[%s]()", t)
}
}
defaultValue := strings.Join(defaultValues, ", ")
replacement := fmt.Sprintf("%s, err := %s(%s)\n\tif err != nil {\n\t\treturn %s\n\t}", code[match[2]:match[3]], funcName, code[match[6]:match[7]], defaultValue)
code = code[:start] + replacement + code[end:]
}
return code
} I am a beginner. My proposal is 99% certain to be stupid |
maybe we just need a auto return keyword when error occred. func doingSomething(src, dst string) (err error) {
defer func() {
if err != nil {
fmt.Println("some error occred", err)
}
}()
r, err := autoreturn os.Open(src)
defer r.Close()
w, err := autoreturn os.Create(dst)
defer w.Close()
err = autoreturn io.Copy(w, r)
if err != nil {
fmt.Println("handle error by manually")
return err
}
return nil
} |
Author background
if err != nil
. I have forked and created Go error handling libraries.Related proposals
Proposal
Add a new statement
try
that allows for succinct error handling.This is translated to
Zero values are generated for any return types, so to see this in the context of a function:
turns into the following (in-lined) code:
The handler argument is optional
This is translated to
Unlike in previous proposals,
try
is a statement, not an expression.It does not return any values.
When a function returns non-error results, an intermediate error variable must be used.
If an expression returns just an error, it is possible to use
try
directly on the expression without an intermediate variable.For the sake of consistency it may be desireable to not allow this form (possibly enforced by linter rather than compiler).
Discussion Summary
This section summarizes the discussion that took place on the proposal that you don't have to wade through lots of comments. It has been inserted into the original proposal.
The below are points that in theory are easy to resolve by altering the existing proposal:
Below are points that were raised about the benefits and costs of this proposal:
And of course are value judgements about whether the benefits outweigh the costs. For some Go programmers, using anything except
return
for returning creates multiple ways to do the same thing, which is unacceptable. Some try to address this by changing the keyword to something likereturnif
.Background
Existing proposals to improve Go errors taught us that our solution must provide 2 things:
Existing solutions handle the first point well but most have done poorly on the second. With a slight variation on existing error handling proposals, we can provide the second.
Motivation: Go's biggest problem
Recently the Go Developer Survey 2022 Q2 results were released.
Now that Generics have been released, the biggest challenge for Go developers is listed as "Error handling / working with stack traces".
Error handling: missing or poorly implemented in many proposals
This proposals allows the Go programmer to write the exact same code, but more tersely:
In such a simple case, with no error handler, this transformation may not be very valueable. However, even in relatively simple case, consider if the zero values are verbose:
In the above example, programmers are tempted to return the structs as pointers just so they can return nil rather than obfuscate their code with zero values. After this proposal, they can just write:
Additionally, there is the case of "handling the error". Often we want to annotate the error with additional information, at least an additional string. Adding this code that modifies the error before it is returned is what I will refer to as adding an "error handler".
The original draft proposal solution used stacked error handlers, but this has difficulties around composition due to the automatic stacking and code readability since the error handler is invoked implicitly. A second proposal was put forth not long after which implemented try as an expression and without any support for (stacked) error handlers. This proposal had extensive discussion that the author attempted to summarize. In my view this proposal was poor because it did not create any affordances for error handling and instead suggested using defer blocks. Defer blocks are a powerful and orthogonal tool that can solve the problem, but for many normal error handling use cases they are clumsy and introduce incidental complexity.
A solution to the error problem should encourage the Go programmer to add error handling code as needed.
Extending existing solutions with function-based error handling
Composing error handlers can be solved by adding a 2nd parameter to
try
. The second parameter is an errorhandler of typefunc(error) error
or more precisely with generics:type ErrorHandler[E error, F error] func(E) F
.Now we can cleanly write the following code given from the original problem statement:
ThenErr
would be a standard library function for chaining error handlers.The new example dramatically reduces verbosity. Once the reader understands that
try
performs an early return of the error, it increases readability and reliability. The increased readability and reliability comes from defining the error handler code in one place to avoid de-duping it in your mind at each usage site.The error handler parameter is optional. If no error handler is given, the error is returned unaltered, or alternative mental model is that a default error handler is used which is the identity function
type ErrorId[E error] func(err E) E { return err }
The CopyFile example is probably a best case for using
defer
for error handling. This technique can be used with try, but it requires named return variables and a pointer.Conclusion
This proposal allows for:
Please keep discussions on this Github issue focused on this proposal rather than hashing out alternative ideas. Almost all the alternatives have been hashed out already.
Provisional
This proposal should be considered for provisional acceptance. The following will need to be well-specified (some are mentioned below in the appendix):
try
- this should be discussed separately after this proposal is provisionally acceptedAppendix: alternative names
I would be happy with
try
being renamed to anything else. Besides other single words likecheck
,return if
has been proposed.I use
try
in this proposal because it is the shortest word that has been proposed so far.This proposal would leave it to the Go maintainers to decide the best name for the word.
Appendix: lazy error handlers
It is tempting to make error handlers lazy.
This way we don't need to bother with making curried handlers.
I am sure this will appeal to many as seeming to be Go-like. It would work to do it this way.
This proposal has a preference for the function handler over a lazy handler to reduce defects.
The lazy form requires using an intermediate variable 3 times. It is possible in Go to produce a defect by using the wrong error variable name.
A go program generally only needs 3 supporting standard error handling functions in a curried form.
However, we should consider supporting both a lazy handler and a function handler.
Appendix: special usage with defer
We could explore making the
defer
andtry
combination special in that it would accept an error handler function and apply it to the returned error value (if not nil) without requiring a named return valueAppendix: Citations
Appendix: prior art
There are 2 boilerplate reductions from this proposal:
if err != nil {
and using 1 lineI believe the latter is well addressed by this proposal that automatically generates zero values from
return ..., err
. It is unfortunate that no action has been taken on that existing proposal. If this proposal were accepted, I think that in any place where one might usereturn ..., err
one could just usetry
. Ifreturn ..., err
were already possible I thinktry
might not add enough value.This proposal is still open and is equivalent to this proposal without the handler argument. It is suggested to add error handling via handler functions that already have an
if err == nil { return nil }
guard. But then using handlers requires reading code and looking at the calling function call to understand how it works and to ensure that it works properly.There have been proposals for dispatching on or after an error value assignment. These are quite similar to this proposal but suffer from being tied to assignment.
This proposal is different, but notes that it adds a
with
keyword for the handler. We could do that for this proposal, but it seems preferable to only reserve one keyword and use a comma.I made a similar proposal in which
try
was an expression rather than a statement.Appendix: generic enumerations
Now that Go has Generics, we might hope for this to get extended to enumerations and have a
Result
type like Rust has. I believe that when that day comes we can adapttry
to work on thatResult
type as well.Appendix: implementation
The try library implements this proposal as a library function. However, it has several shortcomings as a library function that can only be resolved by building features into the Go language itself.
Appendix: code coverage
There are concerns about code coverage. It may be a significant burden for line-oriented code coverage tools to figure out how to tell users if the error paths are getting exercised. I would hate for a helpful tool to hold back back language progress: it is worth it for the community to undertake the effort to have code coverage tools that can determine whether the error path of
try
is getting exercised.Appendix: examples
Appendix: real world code base examples
I did some automated language transforms to use try on the golang codebase. This is easily automated now with Semgrep rules and a little shell script so I could apply this to any code base. Unfortunately it is only examples of using try without an error handler. Try with an error handler is much more difficult to automate.
Costs
Harder to learn the language spec because users must learn a new keyword. However, when verbose error handling code is removed, beginners will be able to read and evaluate Go code more quickly and learn Go faster.
The costs are discussed in detail elsewhere
go fix
for upgradingAll, but those that re-use Go libraries may just need to upgrade their library usage?
I think these tools would then have less source code to analyze and thus run more quickly.
Linters that check that all errors are handled currently use a lot of CPU.
These could be simplified or even removed entirely.
Without handlers I would think it could be reduced because the compiler can avoid evaluating code that previously would have been hand-written. It will reduce the error linting time more significantly (see above) for those of us that run linters right after compilation since checking for proper error handling will be easier.
I think this should be equivalent. The success and error path should be the same. However, having better error handling abilities will encourage Go programs to better annotate their errors. But the error path should not be a performance concern.
I started a branch that gives some idea of some of the changes required, but keep in mind that it is incomplete and already making implementation mistakes.
This can be roughly implemented as a library, done here.
However, it is limited and that can only be solved with compiler modifications.
Internally it uses panic/recover.
defer
at the top of the function and using a pointer to a named return variable for the errorThe text was updated successfully, but these errors were encountered: