This is a translator of 'TryGo' as my experiment to see what happens if Go were having try() function.
Basic idea of try() came from Rust's try! macro (or ? operator). try() handles if err != nil
check implicitly.
This package provides a code translator from TryGo (Go with try()) to Go.
Go:
func CreateFileInSubdir(subdir, filename string, content []byte) error {
cwd, err := os.Getwd()
if err != nil {
return err
}
if err := os.Mkdir(filepath.Join(cwd, subdir)); err != nil {
return err
}
p := filepath.Join(cwd, subdir, filename)
f, err := os.Create(p)
if err != nil {
return err
}
defer f.Close()
if _, err := f.Write(content); err != nil {
return err
}
fmt.Println("Created:", p)
return nil
}TryGo:
func CreateFileInSubdir(subdir, filename string, content []byte) error {
cwd := try(os.Getwd())
try(os.Mkdir(filepath.Join(cwd, subdir)))
p := filepath.Join(cwd, subdir, filename)
f := try(os.Create(p))
defer f.Close()
try(f.Write(content))
fmt.Println("Created:", p)
return nil
}There is only one difference between Go and TryGo. Special magic function try() is provided in TryGo.
try looks function, but actually it is a special operator. It has variadic parameters and variadic
return values. In terms of Go, try looks like:
func try(ret... interface{}, err error) (... interface{})
Actually try() is a set of macros which takes one function call and expands it to a code with error
check. It takes one function call as argument since Go only allows multiple values as return values
of function call.
In following subsections, $zerovals is expanded to zero-values of return values of the function.
For example, when try() is used in func () (int, error), $zerovals will be 0. When it is used
in func () (*SomeStruct, SomeInterface, SomeStruct, error), $zerovals will be nil, nil, SomeStruct{}.
Implementation:
- Definition statement
- Assignment statement
- Call statement
- Call Expression
$Vars := try($CallExpr)
var $Vars = try($CallExpr)
Expanded to:
$Vars, err := $CallExpr
if err != nil {
return $zerovals, err
}
var $Vars, err = $CallExpr
if err != nil {
return $zerovals, err
}
$Assignee = try($CallExpr)
Expanded to:
var err error
$Assignee, err = $CallExpr
if err != nil {
return $zerovals, err
}
Assignment operation x op= y (e.g. x += y) is supported.
$Assignee op= try($CallExpr)
Expanded to:
$tmp, err := $CallExpr
if err != nil {
return $zerovals, err
}
$Assignee op= $tmp
try($CallExpr)
Expanded to:
if $underscores, err := $CallExpr; err != nil {
return err
}
$underscores, is a set of _s which ignores all return values from $CallExpr. For example, when
calling func() (int, error), it is expanded to _. When calling func() (A, B, error) in try(),
it is expanded to _, _. When calling func() error in try(), it is expanded to an empty.
try() call except for toplevel in block
1 + try($CallExpr)
Expanded to:
$tmp, err := $CallExpr
if err != nil {
return $zerovals, err
}
1 + $tmp
This should allow nest. For example,
1 + try(Foo(try($CallExpr), arg))
$tmp1, err := $CallExpr
if err != nil {
return $zerovals, err
}
$tmp2, err := Foo($tmp1, arg)
if err != nil {
return $zerovals, err
}
1 + $tmp2
The order of evaluation must be preserved. For example, when try() is used in a slice literal element,
elements before the element must be calculated before the if err != nil check of the try().
For example,
ss := []string{"aaa", s1 + "x", try(f()), s2[:n]}
will be translated to
tmp1 := "aaa"
tmp2 := s1 + "x"
tmp3, err := f()
if err != nil {
return $zerovals, err
}
ss := []string{tmp1, tmp2, tmp3, s2[:n]}
try()cannot take other than function call. For example,try(42)is ill-formed.try()is expanded to code includingreturn. Using it outside functions is ill-formed.- When function called in
try()invocation does not returnerroras last of return values, it is ill-formed.
These ill-formed code should be detected by translator and it will raise an error.
Following code may look even better. At least I think so.
func CreateFile(subdir, filename string, content []byte) error {
cwd := os.Getwd()?
os.Mkdir(filepath.Join(cwd, subdir))?
f := os.Create(filepath.Join(cwd, subdir, filename))?
defer f.Close()
f.Write(content)?
return nil
}The reason why I adopted try() function is that ...TODO
Download an executable binary from release page (NOT YET).
To build from source:
$ go get -u github.com/rhysd/trygo/cmd/trygo
$ trygo -o {outpath} {inpaths}
{inpaths} is a list of directory paths of Go packages you want to translate. The directories are
translated recursively. For example, when dir is passed and there are 2 packages dir and dir/nested,
both packages will be translated.
{outpath} is a directory path where translated Go packages are put. For example, when dir is specified
as {inpaths} and out is specified as {outpath}, dir/** packages are translated as out/dir/**.