Skip to content
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: add a special returntype "return" #42811

Closed
D4v1dW3bb opened this issue Nov 24, 2020 · 6 comments
Closed

proposal: Go 2: add a special returntype "return" #42811

D4v1dW3bb opened this issue Nov 24, 2020 · 6 comments
Labels
error-handling Language & library change proposals that are about error handling. FrozenDueToAge LanguageChange v2 A language change or incompatible library change
Milestone

Comments

@D4v1dW3bb
Copy link

D4v1dW3bb commented Nov 24, 2020

Is solved with existing functionality: defer and panic

Because I've googled for a long time to find a solution and did not find any I have made an example of an implementation with defer, penic and recover. So maybe someone can use it. I have made one function with no return type, one with one return type and one with two return types. I have meed a simple one because the application where I needed it is much more complex. If you have questions send me a pm.

start reading in fun main

package main

import (
	b64 "encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"log"
)

type singleReturn struct {
	Data string `json:"data"`
}

type multipleReturn struct {
	Data string `json:"data"`
	Done bool   `json:"done"`
}

func dataAction(data string) string {
	return data + data
}

func finishFunction(done bool, data string) {
	if done {
		panic(data)
	}
	return
}

func multipleFinishFunction(done bool, data string) {
	if done {
		ret := multipleReturn{Data: data, Done: done}
		retStr, _ := json.Marshal(ret)
		encrStr := b64.StdEncoding.EncodeToString([]byte(retStr))
		panic(encrStr)
	}
	return
}

// noReturnTypeHelper is called by NorReturnTypeFunction 
func noReturnTypeHelper(data string) {
	log.Println("Doing something with data")
	data = dataAction(data) // Action on data
	done := false           // result of action on data
	finishFunction(done, "noReturnTypeHelper finished with the first try!")

	log.Println("Doing something more with data")
	data = dataAction(data) // action on data
	done = true             // result of action on data
	finishFunction(done, "noReturnTypeHelper finished with the second try!")

	log.Println("Nothing can't be done anymore")
	log.Println("Continue parent function")
	return
}

// singleReturnTypeHelper is called by SingleReturnTypeFunction 
func singleReturnTypeHelper(data string) string {
	log.Println("Doing something with data")
	data = dataAction(data) // Action on data
	done := false           // result of action on data
	multipleFinishFunction(done, data)

	log.Println("Doing something more with data")
	data = dataAction(data) // Action on data
	done = true             // result of action on data. Could be false and then data is returned to parent function
	multipleFinishFunction(done, data)

	log.Println("Nothing can't be done anymore, returning data back to parent function")
	return data
}


// multipleReturnTypeHelper is called by MultipleReturnTypeFunction 
func multipleReturnTypeHelper(data string) (string, bool) {
	log.Println("Doing something with data")
	data = dataAction(data) // Action on data
	done := false           // result of action on data
	multipleFinishFunction(done, data)

	// Option 1
	// comment this option and uncomment option 2 to let this function return normal (first MultipleReturnTypeFunction call will fail)
	log.Println("Doing something more with data")
	data = dataAction(data) // Action on data
	done = true             // result of action on data. Could be false and then data is returned to parent function
	multipleFinishFunction(done, data)

	// Option 2 uncomment this option and comment option 1 to let this function return normal
	//log.Println("Doing something more with data")
	//data = dataAction(data) // Action on data
	//done = false            // result of action on data. Could be false and then data is returned to parent function
	//multipleFinishFunction(done, data)

	log.Println("Nothing can't be done here anymore, returning data back to parent function")
	// Option 2.1
	// When Option 2 is active and you leave the next commant commented "do something else with data" wil be printed and the data wil be returned
	// if jou uncomment it the Second MultipleReturnTypeFunction wil print out "Data processing not complete or Data was corrupted"
	//done = true
	return data, done
}

// NoReturnTypeFunction cals a function who ends this function and returns nothing
func NoReturnTypeFunction(data string) {
	defer func() {
		if abort := recover(); abort != nil {
			log.Println("NoReturnTypeFunction Finished early")
		}
	}()
	log.Println("NoReturnTypeFunction Starts")
	noReturnTypeHelper(data)
	log.Println("After first noReturnTypeHelper call")

	noReturnTypeHelper(data)
	log.Println("After second noReturnTypeHelper call")
	log.Println("NoReturnTypeFunction Finished Normal")
	return
}

// SingleReturnTypeFunction cals a function that returns a single result
// which then is returned by this function
func SingleReturnTypeFunction(data string) (retData string) {
	defer func() {
		if abort := recover(); abort != nil {
			jsonData := fmt.Sprintf("%s", abort)
			strData := singleReturn{}
			_ = json.Unmarshal([]byte(jsonData), &strData)
			retData = strData.Data
			log.Println("SingleReturnTypeFunction Finished early")
		}
	}()
	log.Println("SingleReturnTypeFunction Starts")
	data = singleReturnTypeHelper(data)
	log.Println("After first singleReturnDataHelper call")

	data = singleReturnTypeHelper(data)
	log.Println("After second singleReturnDataHelper call")
	log.Println("singleReturnTypeFunction Finished Normal")
	retData = data
	return
}

// MultipleReturnTypeFunction cals a function that returns multiple results
// which then are returned by this function
func MultipleReturnTypeFunction(data string, mpl int) (retString string, err error) {
	size := len(data)
	defer func() {
		if abort := recover(); abort != nil {
			jsonData, _ := b64.StdEncoding.DecodeString(fmt.Sprintf("%s", abort))
			strData := singleReturn{}
			_ = json.Unmarshal([]byte(jsonData), &strData)
			if len(strData.Data) == mpl*size {
				retString = strData.Data
				err = nil
			} else {
				retString = ""
				err = errors.New("Data was corrupted")
			}
			log.Println("MultipleReturnTypeFunction Finished early")
		}
	}()
	done := false
	log.Println("MultopleReturnTypeFunction Starts")
	data, done = multipleReturnTypeHelper(data)
	log.Println("After first multipleReturnDataHelper call")

	data, done = multipleReturnTypeHelper(data)
	log.Println("After second multipleReturnDataHelper call")
	log.Println("multipleReturnTypeFunction Finished Normal")
	if len(data) == mpl*size && done {
		retString = data
		err = nil
	} else if done {
		retString = ""
		err = errors.New("Data processing not complete or Data was corrupted")
		return
	}
	log.Println("Do something else with data")
	retString = data
	err = nil
	return
}

func main() {
	// Simple defer, panic, recover functionality
	NoReturnTypeFunction("No")
	// Actualy the same as the first only difference is that i return the string given as parameter
	// for panic is asigned to the named return value of the function in the defer function.
	SingleReturnTypeFunction("Single")
	// Little more complex the different values are stort in a struct, json Marshald and base64 encoded 
	// and then put into panic as a parameter. And in the defer statement base64 decoded and json UnMarshald.
	// This one will succeed when option 1 in multipleReturnTypeHelper is uncommented
	// and will fail if option 1 is commented and option 2 is uncommented in multopleReturnTypeHelper
	string, err := MultipleReturnTypeFunction("Multiple", 4)
	if err != nil {
		log.Println(err)
	} else {
		log.Printf("Data: %s", string)
	}
	// Next one will fail when option 1 is uncommented
	// If option 1 is commented and option 2 is uncommented and option 2.1 is commented in multipleReturnTypeHelper
	// This one will print "Do something else with data"
	// If option 2.1 is uncommented it will print "Data processing not complete or Data was corrupted"
	string, err = MultipleReturnTypeFunction("Multiple", 8)
	if err != nil {
		log.Println(err)
	} else {
		log.Printf("Data: %s", string)
	}
}
@mvdan
Copy link
Member

mvdan commented Nov 24, 2020

Please note that you should fill https://github.com/golang/proposal/blob/master/go2-language-changes.md when proposing a language change.

@mvdan mvdan added WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided. LanguageChange labels Nov 24, 2020
@D4v1dW3bb
Copy link
Author

D4v1dW3bb commented Nov 24, 2020

Would you consider yourself a novice, intermediate, or experienced Go programmer?
Intermediate. 10 months using Go.
• What other languages do you have experience with?
C/C++, C#, Python, Java, JavaScript, PHP, Ruby on Rails.
• Would this change make Go easier or harder to learn, and why?
I expect this proposal has little to no effect on the learning curve to learn Go.
• Has this idea, or one like it, been proposed before?
No, at least I did not find any.
• Who does this proposal help, and why?
Everybody who uses repeating if statements in a function that contain a return to end the function. The amount of code can be reduced even more. I will use a error handling as an example but over the years I have encountered multiple situations where I could have used such a functionality.
• What is the proposed change?
Adding a “return” result type to a function that inflicts a return action when true is returned.
o Please describe as precisely as possible the change to the language.
If the first result type is “return” and “true” is returned by the function a return is initiated at the point where the function was initiated.
If other return types follow “true” those are past true to the return action initiated by the function.
If the first result type is “return” and “false” is returned by the function, nothing is returned
o What would change in the language spec?
Nothing.
o Please also describe the change informally, as in a class teaching Go.
We want to get rid of redundant code as much as possible and put it in a function. When building handlers in Go we have to check regularly for errors and end the handler function when an error occurs. Till now we used an if statement with some actions and a return in it that would be executed when an error occurred and put this in every place we needed. If we were lucky we could combine the actions into a function. We could never get rid of the if statement and the return.
With the new result type “return” you can put the whole if statement in a function.
Give the variables needed within the if statement as parameters to the function and put “return” as the return type, optional followed by other return types. In the if statement put “return true”, optional followed by the other return types.
After the if statement you write “return false”
• Is this change backward compatible?
The only situation where it could not be backward compatible is when a developer used “return” as the name for a return type and true is returned for that name.
Show example code before and after the change.

Before:

func getReport(reportID int) (*Report, error) {
	row := database.DbConn.QueryRow(`SELECT reportId,
	title,
	subTitle,
	status,
	maintainerID,
	results,
	body
	FROM reports
	WHERE reportId = ?`, reportID)
	var report Report
	err := row.Scan(&report.ReportID,
		&report.Title,
		&report.SubTitle,
		&report.Status,
		&report.MaintainerID,
		&report.Results,
		&report.Body)
	if err == sql.ErrNoRows {
		return nil, nil
	} else if err != nil {
		return nil, err
	}
	return &report, nil
}

func handleReport(w http.ResponseWriter, r *http.Request) {
  urlPathSegments := strings.Split(r.URL.Path, fmt.Sprintf("%s/", reportsPath))
  if len(urlPathSegments[1:]) > 1 
  w.WriteHeader(http.StatusBadRequest)
    return
  }

  reportID, err := strconv.Atoi(urlPathSegments[len(urlPathSegments)-1])
  if err != nil {
    log.Print(http.StatusNotFound)
    w.WriteHeader(http.StatusNotFound)
    return
  }

  switch r.Method {
  case http.MethodGet:
    report, err := getreport(reportID)
    if err != nil {
      log.Print(err)
      w.WriteHeader(http.StatusInternalServerError)
      return
    }
    if report == nil {
      w.WriteHeader(http.StatusInternalServerError)
      return
    }
    j, err := json.Marshal(report)
    if err != nil {
      log.Print(err)
      w.WriteHeader(http.StatusBadRequest)
      return
    }
    _, err = w.Write(j)
    if err != nil {
      log.Fatal(err)
    }

  case http.MethodPut:
    var report report
    err := json.NewDecoder(r.Body).Decode(&report)
    if err != nil {
      log.Print(err)
      w.WriteHeader(http.StatusBadRequest)
      return
    }
    if report.reportID != reportID {
      w.WriteHeader(http.StatusBadRequest)
      return
    }

    err = updatereport(report)
    if err != nil {
      log.Print(err)
      w.WriteHeader(http.StatusBadRequest)
      return
    }

  case http.MethodDelete:
    err := removereport(reportID)
    if err != nil {
      log.Print(http.StatusNotFound)
      w.WriteHeader(http.StatusNotFound)
      return
    }

  case http.MethodOptions:
    return

  default:
    w.WriteHeader(http.StatusMethodNotAllowed)
  }
}

after:

func checkError(b bool, w http.ResponseWriter,  header int, err interface{}) return {
  if b {
    if err != nil {
      log.Print(err)
    }
    w.WriteHeader(header)
    return true
  }
  return false
}

func getReport(reportID int) (return, *Report) {
	row := database.DbConn.QueryRow(`SELECT reportId,
	title,
	subTitle,
	status,
	maintainerID,
	results,
	body
	FROM reports
	WHERE reportId = ?`, reportID)
	var report Report
	err := row.Scan(&report.ReportID,
		&report.Title,
		&report.SubTitle,
		&report.Status,
		&report.MaintainerID,
		&report.Results,
		&report.Body)
	if err == sql.ErrNoRows {
		log.Print(err)
		w.WriteHeader(http.StatusInternalServerError)
		return true, nil
	}
	if report == nil {
		w.WriteHeader(http.StatusInternalServerError)
		return true, nil
	}
	return false, &report
}

func handleReport(w http.ResponseWriter, r *http.Request) {
  urlPathSegments := strings.Split(r.URL.Path, fmt.Sprintf("%s/", reportsPath))
  checkError(len(urlPathSegments[1:]) > 1, nil, w, http.StatusBadRequest, nil)

  reportID, err := strconv.Atoi(urlPathSegments[len(urlPathSegments)-1])
  checkError(err != nil, w, http.StatusNotFound, err)

  switch r.Method {
  case http.MethodGet:
    _, report := getreport(reportID)

    j, err := json.Marshal(report)
    checkError(err != nil, w, http.StatusBadRequest, err)

    _, err = w.Write(j)
    if err != nil {
      log.Fatal(err)
    }

  case http.MethodPut:
    var report report
    err := json.NewDecoder(r.Body).Decode(&report)
    checkError(er != nil, w, http.StatusBadRequest, err)
    checkError(report.reportID != reportID, w, http.StatusBadRequest, nil)

    err = updatereport(report)
    checkError(err != nil, w, http.StatusBadRequest, err)

  case http.MethodDelete:
    err := removereport(reportID)
    checkError(err != nil, w, http.StatusNotFound, err)

  case http.MethodOptions:
    return

  default:
    w.WriteHeader(http.StatusMethodNotAllowed)
  }
}

• What is the cost of this proposal? (Every language change has a cost).
Someone has to implement it.
o How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?
Minimum to non, accept in the backwards compatible scenario.
o What is the compile time cost?
The proposal does not really do anything different than making functions of recurring code. I expect not really a difference.
o What is the run time cost?
Same as previous point .
• Can you describe a possible implementation?
No.
• How would the language spec change?
The new feature has to be added to the functions section of the language spec.
• Orthogonality: how does this change interact or overlap with existing features?
I can’t see that this proposal does conflicts or overlaps with existing features.
• Is the goal of this change a performance improvement?
Les code is more performance I would say.
o If so, what quantifiable improvement should we expect?
I went from 68 lines to 50 lines that’s a reduction of almost 25%.
o How would we measure it?
Go and look how often if statements are used containing a return command.
• Does this affect error handling?
No.
• Is this about generics?
No.

@ianlancetaylor ianlancetaylor changed the title Proposal for adding a special returntype "return" proposal: Go 2: add a special returntype "return" Nov 25, 2020
@gopherbot gopherbot added this to the Proposal milestone Nov 25, 2020
@ianlancetaylor ianlancetaylor added error-handling Language & library change proposals that are about error handling. v2 A language change or incompatible library change and removed Proposal WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided. labels Nov 25, 2020
@ianlancetaylor
Copy link
Contributor

What should happen if a function with this special return type is called by a function that itself has results? All of your examples seem to be calls from a function that does not have results.

@D4v1dW3bb
Copy link
Author

D4v1dW3bb commented Nov 25, 2020

What should happen if a function with this special return type is called by a function that itself has results? All of your examples seem to be calls from a function that does not have results.

I'm making a full example. Takes a little more time

@seankhliao
Copy link
Member

wouldn't this face the same pushback as try() where control flow was too hidden

see also #35093

@D4v1dW3bb
Copy link
Author

thanks @seankhliao That's exact the functionality what I was looking for. Sorry for bothering you all.

@golang golang locked and limited conversation to collaborators Dec 1, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
error-handling Language & library change proposals that are about error handling. FrozenDueToAge LanguageChange v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests

5 participants