go: proposal: Go 2: Error handling based on the pointers to instance of goroutine, function or method

questionnaire.txt

When a function is called, the pointer to its instance is stored in the variable for future access. When an error is thrown, its type is associated with the pointer to function instance. For catching an error, you must specify the pointer to function instance and the error type or error metadata. You can then operate the function instance in which the error occurred, or any other function instance, if you have the pointer to it.

// this proposal introduces significant changes to the Go language
// error handling is the first most important problem in Golang after generics
// therefore, to solve it, significant changes can be made in version 2.0

// THROW ERROR
throw("insignificant_error") 
// error and log record must be available instantly, well before the function returns
// each function can throw several different errors
// the compiler must make sure that every error in the form of a string literal is catched
throw(expr) // any expression of string type
exit("significant_error") // throw the error and exit from the current function
// "defer" also works in this case

// GET POINTER TO GOROUTINE INSTANCE WHERE THE ERROR OCCURRED
go f#p() // the pointer to the new goroutine instance is assigned to the variable f#p

// GET POINTER TO FUNCTION INSTANCE OR TO METHOD INSTANCE WHERE THE ERROR OCCURRED
// it is a pointer to the INSTANCE of function, not a pointer to the function
z = f#p1(x) + f#p2(y) // pointers to function f() instances are assigned to the variables f#p1 and f#p2
// there is no need to implicitly declare and/or initialize such variables
// their first value cannot be changed
// they are visible everywhere, and therefore no error wrapping is needed
// and so this kind of program code is completely unnecessary:
	r, err := function_of_proposed_style()
	if err != nil {
		return err
	}
//
x := math.Abs#p7(z)
f#p()
// I ask functional programming experts to invent a syntax for getting a pointer
// to function instance for functional programming concepts in Go and for interfaces
// https://en.wikipedia.org/wiki/Functional_programming#Concepts

// CATCH ERRORS FROM SPECIFIED FUNCTION/METHOD/GOROUTINE INSTANCE
if exists(f#p, "message_type") { // one specified error
// "exists" returns boolen value of message existanse
// for cancelled errors it returns false
stop(f#p)
}

if exists(f#p, expr) {  // any expression of string type

if severity(f#p) > "warning" { // significant errors exist; severity level is an ordered enumeration, not string
// opton:  if severity(f#p) > level["warning"]  where level is a map
// see severity levels: https://en.wikipedia.org/wiki/Java_logging_framework#Severity_level

if severity(f#p) <= "warning" { // significant errors do not exist; severity level is an ordered enumeration, not string
// opton:  if severity(f#p) <= level["warning"]  where level is a map

switch {
    case exists(f#p, "message_type_1"):
        /* do something */
		fallthrough
    case exists(f#p, "message_type_2", "message_type_3", "message_type_4"): // any of listed errors
        /* do something else */
		fallthrough
    default: // all the other significant errors
		if severity(f#p) > "warning" { //severity level is an ordered enumeration, not string
		// opton:  if severity(f#p) > level["warning"]  where level is a map
		/* do something else */ 
		}
}

// CATCH ERRORS FROM ANY INSTANCE OF THE FUNCTION/METHOD/GOROUTINE
if exists(f, "message_type") { 

// CANCEL ERROR
cancel(f#p, "message_type")

// READ METADATA ON ERROR FROM LOG AND FROM TABLE OF ERROR TYPES
// (LOG IS FILLED AUTOMATICALLY)
struct_x = message(f#p, "message_type") // "message" returns a log record of struct type
// log records cannot be deleted when the error or another message is canceled
if message(f#p, "message_type").field_name == some_variable { 
// "field_name" may be package, file, line, timestamp, status, identifier, value, 
// severity, description, url, pointer_to_messageType, pointer_to_message etc.

// FILL TABLE OF ERROR TYPES
messageType("message_type").field_name = "field_value" // "messageType" is visible everywere
// for example:
messageType("error_432").severity = "fatal" 
messageType("error_432").description = "..."
messageType("error_432").url = "..."
// pointer to messageType is filled automatically

// OPERATE GOROUTINE INSTANCE
stop(f#p) // the pointer to desired function instance is extracted from the variable f#p and used
// "stop(f#p)" means the same as "return" in the function f() body. Sometimes it's better than `panic`
// use it for reliable goroutine that should either be a pure function (linter should check it),
// or it should have recovery mode, or it should solve this problem with defer,
// or it should work without error (then you don't have to stop it)
// "defer" also works in this case
// a new special operator 'stopper' alike 'defer' can be added to run the function when the goroutine is stopped
// anyway runtime could free all the resources occupied by the stopped goroutine
stop(f1#p1, f1#p2, f3#p) // for several function instances
pause(f#p)
resume(f#p)
recompute(f#p, mode) // parameters of function f() could be updated beforehand
// optional "mode" may be safe mode, recovery mode, test mode, demo mode, 
// normal mode, developer mode, factory mode, custom mode etc.
await(f#p)

// RECOMPUTE STATEMENT AS GENERATOR OR FINITE-STATE MACHINE (CONTINUATION)
store(optional_parameter_list)
recall(optional_parameter_list)

// INTEGRATION OF LEGACY FUNCTIONS/METHODS
// os.Open() is a legacy
// therefore instead of
r, err := os.Open("C:\Users\Helen\Desktop\skill.csv")
if err != nil {
	return err
}
// in the body of function f() you should write in the body of function f()
r, err := os.Open("C:\Users\Helen\Desktop\skill.csv")
if err != nil {
	throw(err)
}
// and than handle the error received from the called function f() in the caller function
go f#p()
if exists(f#p, err) {
pause(f#p)
fmt.Println(err)
wait_for_user_input#k()
}
// or you can handle every possible option (type) of err separately

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 18
  • Comments: 16 (6 by maintainers)

Most upvoted comments

I’m going to rather bluntly state that, on the face of it, this seems extremely unlikely to be accepted. The language changes proposed here are incredibly far-reaching, contradict a number of explicit goals of the language, and would not mesh well with the existing concurrency model. What does stop mean for a goroutine? pause? These concepts are today totally alien to existing codebases and, as @seebs pointed out in some of his comments on the last issue, would fundamentally break a number of today-safe assumptions that large amounts of correct go code today rely upon.

The proposal does not address a number of real issues pointed out in the previous proposal, either, around anonymous functions and interfaces, instead asking for the developers to fill in the gaps – nevermind that it may be categorically impossible to do so with the proposed functionality as designed.

The idea of doing such a root-and-branch restructuring of the language at this point in the lifecycle of the language would be far, far more disruptive than the python 2->3 split was, and that took the better part of, what, a decade? to get everyone on board.

And all of this in service of error handling, which is a topic which is something of a third-rail for proposals at this point.

@sergeyprokhorenko you have interesting ideas, and maybe a language of your own would be a good place to explore them. But, and I speak for no one but myself on this one, I kinda doubt that the go proposal process is going to ever be a productive way to see them to fruition.

It seems clear to me that this discussion is going nowhere, and that these changes are too drastic for the language we know today as Go, even in a future major version.

In the interest of saving time and further arguments, I am going to close this out. Please discuss your ideas further on the mailing list, preferably with an actionable prototype.

Compiler should not disallow to stop any goroutine, because sometimes it’s the only way to avoid disaster at any price.

@sergeyprokhorenko can you provide more detail on what sort of situation you’ve encountered where you needed to kill a green thread to “avoid disaster at any price”. Most cases I can think of would be handled by a context.Context, and I’m curious on why that doesn’t work in that scenario. The more details you’re able to provide, I think the better it’ll describe the value of your proposal.

I understand that you are being forced to use software made by negligent lazy idiots who never read and write the technical documentation. But not all programmers are like that, and they need a tool to shut down a program that has gone haywire.

No, it’s that humans make mistakes. Or worse, that humans make good decisions based on incorrect information that then result in bad outcomes. In complex sociotechnical systems, where reliability and resiliency are two of your primary goals, you don’t want to increase the cognitive hurdles that people need to overcome to achieve success. You want to reduce them.

By not having the safety aspect of what you’re looking to introduce be a core part of the feature, you’re setting up Gophers to fail for years. Not because they are lazy, incompetent, stupid, or bad; but because they are a human, and will make mistakes, or have outdated or incomplete context.

You should expand your horizons, and look into the Human Factors & System Safety field of research. I think it would compliment your desire to build resilient systems.

Every reliable goroutine should either be a pure function (linter should check it), or it should have recovery mode, or it should solve this problem with defer, or it should work without error (then you don’t have to stop it).

Then I think you need to expand your proposal to include this change, otherwise it feels incomplete to me.

Thinking, of course, is too hard 😃

I don’t appreciate your elitist attitude, because, frankly, everything I’ve seen thus far indicates it’s misplaced. I think you should follow the advice of others, and explore this idea with a fork of the language and reopen the proposal once you’ve a more concrete answer to some of these questions, and more of a clear migration path to being able to include those features in the language.

I think that will also help others better appreciate the proposal, if your goals do come to fruition via the changes.

@deltamualpha Do not impose your point of view on the authors of the language, and do not turn technical discussions into political discussions. Let’s leave decision making to those responsible.

Error handling is the first most important problem in Golang after generics. Therefore, to solve it, significant changes can be made in version 2.0.

@theckman You took my words out of context and distorted their meaning

so it sounds like your proposal depends on an aspect of the language that is probably not universally true, which is that goroutines are pure functions. How would someone know a goroutine is safe to terminate or not? How does the goroutine declare that and the compiler disallow it?

@theckman Sane programmers read the technical documentation (about safe termination) before using anything alike panic or stop. Compiler should not disallow to stop any goroutine, because sometimes it’s the only way to avoid disaster at any price.

This sounds like it’s going to introduce a substantial amount of cognitive overhead, that’s going to require developers to read every line of code of things they invoke, so that they can know whether or not it’s safe to stop. Similarly, if a goroutine becomes unsafe to terminate because of changes elsewhere in the codebase, how do the software engineers reliably detect that at build time?

I understand that you are being forced to use software made by negligent lazy idiots who never read and write the technical documentation. But not all programmers are like that, and they need a tool to shut down a program that has gone haywire. Every reliable goroutine should either be a pure function (linter should check it), or it should have recovery mode, or it should solve this problem with defer, or it should work without error (then you don’t have to stop it).

This seems unsafe and in need of more consideration, considering it’s not something Gophers need to think about now.

Thinking, of course, is too hard 😃

You can search the log for attribute values of the specified error, or vice versa, search for errors with specific attribute values. If you find an error, then you will get the type of error and the pointer to the function instance. Based on this data, you can do whatever you want with this function instance.

This is the first time you’ve used the term “attribute values” in this entire issue. What does that mean? How does it work? What happens if two different errors share the same attributes and are of different types?

“attribute values” are the same as error metadata in log. We should give it to programmer. But it is the responsibility of the programmer to decide how to identify errors, which errors to consider and how to handle them.

It’s also unclear how a goroutine indicates the places where it’s safe to be stopped or paused, and how that is managed by the runtime. How do you avoid this construct creating invalid program state, by interrupting a goroutine that’s in the process of doing some complex task? You can’t always make use of deferred statements to avoid that.

@theckman Goroutine does not indicate the places where it’s safe to be stopped or paused. It, like the car in front of you, can brake at any moment, but you must keep a safe distance. If the goroutine is a pure function, then it can be safely stopped at any time. Otherwise, you have to rely on defer, but it’s better to use the recompute statement in recovery mode instead of the stop statement. In this mode of the goroutine (it’s a separate algorithm), all necessary measures must be provided to eliminate the negative consequences of a sudden stop of the goroutine. If you haven’t taken care of this, then you shouldn’t stop the goroutine. If you have a machine gun, this does not mean that you can shoot anytime when you want. But there may be situations where it is safer to stop an out-of-control goroutine despite the consequences of a sudden stop.

@sergeyprokhorenko so it sounds like your proposal depends on an aspect of the language that is not universally true, which is that goroutines are pure functions. How would someone know a goroutine is safe to terminate or not? How does the goroutine declare that and the compiler disallow it?

This sounds like it’s going to introduce a substantial amount of cognitive overhead, that’s going to require developers to read every line of code of things they invoke, so that they can know whether or not it’s safe to stop. Similarly, if a goroutine becomes unsafe to terminate because of changes elsewhere in the codebase, how do the software engineers reliably detect that at build time?

This seems unsafe and in need of more consideration, considering it’s not something Gophers need to think about now.

You can search the log for attribute values of the specified error, or vice versa, search for errors with specific attribute values. If you find an error, then you will get the type of error and the pointer to the function instance. Based on this data, you can do whatever you want with this function instance.

This is the first time you’ve used the term “attribute values” in this entire issue. What does that mean? How does it work? What happens if two different errors share the same attributes and are of different types?

Also, how do you handle complex error types?

@theckman See the section // READ METADATA ON ERROR FROM LOG AND FROM TABLE OF ERROR TYPES of the proposal.

You can search the log for attribute values of the specified error, or vice versa, search for errors with specific attribute values. If you find an error, then you will get the type of error and the pointer to the function instance. Based on this data, you can do whatever you want with this function instance.

@sergeyprokhorenko in your questionnaire you have

Q: Would this change make Go easier or harder to learn, and why? A: Easier, because error handling will become more obvious and concise

Yet in the same document you offer a comparison of the before and after, and the after code feels much less obvious and concise. Do you have a different comparison you can provide that better highlights your desired outcomes?