Skip to content

Instantly share code, notes, and snippets.

@Kiura
Created September 26, 2018 14:37
Show Gist options
  • Save Kiura/4826db047e22b7720d378ac9ac642027 to your computer and use it in GitHub Desktop.
Save Kiura/4826db047e22b7720d378ac9ac642027 to your computer and use it in GitHub Desktop.
Kiura Magomadov "Addition to Go2 draft error handling" September 2018

This is just another way to deal with errors following Go2 draft structure.

If I understood correctly handle is used as defer in many proposals, which, could be a drawback in case we want the function to fail as soon as error occurs. So, a better scenario could be as follows:

  1. Unhandled error occurs - it is thrown and parent function should handle it
  2. Handled error occurs - handle function catches it and performs some operations before exiting the function (in the handle function one can decide to throw an error again if he/she needs to add additional info to the err)
func CopyFile(src, dst string) throws error {
	r := os.Open(src) // option 1
	defer r.Close()

	w := os.Create(dst)
	err := io.Copy(w, r) // option 2
	err2 := w.Close() // option 2
	handle err || err2 { // handle function is executed if `err` or `err2` is not equal to `nil`
		w.Close()
		os.Remove(dst)
		// optionally throw error here
		// return errors.New("could not copy.")
	}
}

Actually I am not sure why try catch is not considered, it seems it could be the best solution (familiar syntax)

func CopyFile(src, dst string) throws error {
	r := os.Open(src) // thrown so parent function can catch it or pass to its parent
	defer r.Close()

	w := os.Create(dst)
	try { // handle special case
		io.Copy(w, r)
		w.Close()
	} catch err {
		w.Close()
		os.Remove(dst)
		// optionally throw error here
		// return errors.New("could not copy.")
		// or to make it consistent with other languages throw it
		// throw errors.New("could not copy.")
	}
}

Methods without comments:

func CopyFile(src, dst string) throws error {
	r := os.Open(src)
	defer r.Close()

	w := os.Create(dst)
	err := io.Copy(w, r)
	err2 := w.Close()
	handle err || err2 {
		w.Close()
		os.Remove(dst)
	}
}
func CopyFile(src, dst string) throws error {
	r := os.Open(src)
	defer r.Close()

	w := os.Create(dst)
	try {
		io.Copy(w, r)
		w.Close()
	} catch err {
		w.Close()
		os.Remove(dst)
	}
}
@Kiura
Copy link
Author

Kiura commented Jun 22, 2025

Another better idea:

func (uc UserCommand) UpdateUserAge(id, age int) error {
    user, _ := try userService.Get(id) // try only triggered if the returned err is not nil
    user.Age = age
    try userService.Update(user)

    // for the sake of completeness we can also throw errors
    throw fmt.Errorf("need to return early due to unexpected error")
    
    // as many catches as needed
    catch err QueryError {
        return fmt.Errorf("cannot fetch user with id: %d", id)
        // if no return in catch, the error is returned as is
    }
    catch err ValidationError {
        return fmt.Errorf("cannot update, reason: %v", err)
    }
    catch err error {
        return fmt.Errorf("unexpected error: %v", err)
    }
    // if no catch block, the error is returned as is
    return user, nil
}

The best part is that we can pass to parent the error handling

func (uc UserCommand) UpdateUserAge(id, age int) error {
    user, _ := try userService.Get(id)
    user.Age = age
    try userService.Update(user)
    return user, nil
}

Usage:

oldUser, err := uc.UpdateUserAge(1, 19) // no error
oldUser, err := uc.UpdateUserAge(-1, 19) // QueryError
oldUser, err := uc.UpdateUserAge(1, 1000) // ValidationError

@Kiura
Copy link
Author

Kiura commented Jun 22, 2025

Passing to parent is great for many layers:

user-service.go:

func (us UserService) Get(id int) (User, error) {
    user := User{}
    try us.db.NewSelect().Model(user).Where("id = ?", id).Scan(ctx)
    return user, nil
}

func (us UserService) Update(user User) error {
    try us.db.NewUpdate().Model(user).WherePK().Exec(ctx)
}

user-command.go:

func (uc UserCommand) UpdateUserAge(id, age int) error {
    user, _ := try uc.userService.Get(id)
    user.Age = age
    try uc.userService.Update(user)
}

router.go:

    app.Post("/users/:id/update-age", func(c fiber.Ctx) error {
        id, _ := try c.ParamsInt("id")
        user := new(User)
        try c.BodyParser(user)
        
        try userCommand.UpdateUserAge(id, user.Age)
        catch err QueryError {
                return fmt.Errorf("cannot fetch user with id: %d", id)
        }
        // more catches here
        return c.SendString("Update is successful!")
    })

@Kiura
Copy link
Author

Kiura commented Jun 23, 2025

router.go:

    app.Post("/users/:id/update-age", func(c fiber.Ctx) error {
        ...
        // we can use default variable (just like guard in swift)
        msg := try translate("success") ?? "Update is successful!"
        return c.SendString(msg)
    })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment