Signs of Triviality

Opinions, mostly my own, on the importance of being and other things.
[homepage] [index] [jschauma@netmeister.org] [@jschauma] [RSS]

Marbles in my Underpants

I managed to find some time to take a closer look at learning Go (the programming language). So far, I really like what I see. As anticipated, the language feels very comfortable, yet in a manner not entirely unlike when I first started learning Python, I've already had a number of "Hey, that's kind of neat!" moments. Here are a few observations so far.

First of all, following the tutorial is easy and gives you a pretty good overview of the language (even though it seems to assume you have . in your $PATH?); the slides from the 3-day course (Basics, Types, Methods and Interfaces and Concurrency and Communication) provide more detailed information. And then, yesterday, the Go Nuts went more nuts and announced a way to learn Go from your browser via A Tour of Go. All of that makes it quite easy to pick up the basics. Yay.

Setting up myself for playing with Go was easy enough: I can use my favorite text editor (syntax highlighting goodness provided in the Go distribution under misc/vim), so all I need is to invoke the compiler (6g) and the linker (6l) much like when I write C. (Note that there are different compilers/linkers depending on the architecture, which Go inherited from Plan 9's tool chain.) Well, except, I have to actually invoke both, even for single-input-file programs. That's not really a big deal, since for anything reasonably complex we'll need a Makefile anyway, so let's just, uhm, make one.

.SUFFIXES:      .6 .go

PROG=cp

all: ${PROG}

${PROG}: ${PROG}.6
        6l -o $@ $<

.go.6:  
        6g $<

clean:  
        rm -f *.6 ${PROG}



Go - The Very
Best Of Moby

As I work my way through the tutorial and write some code, I find myself thinking that a lot of the design decisions made in the language make sense to me. Minor annoyances so far include the fact that I have to remember to type godoc instead of man (kind of like pydoc) and that godoc doesn't feed into a pager, so that I actually have to type godoc foo | more. Some of the things I like:

Interfaces

"Interfaces provide a simple form of polymorphism. They completely separate the definition of what an object does from how it does it, allowing distinct implementations to be represented at different times by the same interface variable."



String(), fmt.Println

You can print pretty much anything via fmt.Println; objects have a convention of implementing a String() routine.

Idiomatic return values

Functions commonly return a value as well as a success indicator. That is, you frequently encounter function calls like:

val, ok = obj.Func()

and you can toss aside any unwanted values by assigning them to the _ variable:

val, _ = obj.Func()

As a side note, I was amused to find the convention of creating interfaces ending in 'er' after a few days earlier I had just read this blog post. (As those of you who know me are aware, I have no problem holding as true two conflicting statements at the same time.)

Returning more than one value

As in various other programming languages, functions in Go can return more than one value (see above). However, Go requires you to declare that in the function prototype; additionally, you can return specifically named values with a simple return statement:

func GetStringLengths(arg1, arg2 string) (x, y int) {
        x := len(arg1)
        y := len(arg2)
        return
}



Grouping of all sorts of things

As the example above shows, you can group function arguments of the same type just as you can group return values of the same type. You can group import statements and variable or constant declarations. When syntax elements are not needed, they can be left out (semicolons, for example, either at the end of the line or in a for loop):

import (
        "fmt"
        "os"
)

const ( 
        Space = " "
        Newline = "\n"
)

func main() {
        var (   
                a, b, c = 1, 2, 3
                s1, s2 = "foo", "bar"
                v int
        )

        for {   
                // do something, forever
        }
}



defer

You can defer a function call until the surrounding function returns. This is wonderful, as it lets you group your code in blocks that ensure that whatever you opened gets closed, whatever you allocated gets deallocated, whatever resources you're using is cleaned up without having to "remember" to do so after you've used the resource (possibly several pages further down). Think of it as function-specific exit handlers. How cool is that?

Compile-time errors, not warnings

As best as I can tell, the 6g compiler does not have a concept of warnings. All warnings are errors (think -Wall -Werror). For example, an unused variable is an error. Clean it up, yo!


Gordon, the Go Gopher After following the tutorial, I figured I should probably write some code myself, and seeing how Go claims to be a system level programming language, I decided I'd follow the assignments I'm giving my students to learn C, so I went ahead and submitted my own homework assignment of writing a dumb version of cp(1).

package main

import (
        "fmt"
        "os"
        "path"
        "syscall"
)

const _PROGNAME string  = "cp"

/* perform the actual copying */
func copy(src, dest string) {
        var (   
                e, r, rfd, wfd int
                sb1, sb2 syscall.Stat_t
        )

        /* check if src exists */
        e = syscall.Stat(src, &sb1)
        if e != 0 {
                fmt.Fprintf(os.Stderr, "%s: %s: %s\n",
                                _PROGNAME, src, syscall.Errstr(e))
                os.Exit(1)
        }

        /* check if src is a directory */
        if (syscall.S_IFDIR & sb1.Mode) != 0 {
                fmt.Fprintf(os.Stderr, "%s: %s is a directory\n",
                                _PROGNAME, src)
                os.Exit(1)
        }

        /* if dest exists, make sure it's not the same file */
        e = syscall.Stat(dest, &sb2)
        if e == 0 && sb1.Ino == sb2.Ino {
                fmt.Fprintf(os.Stderr, "%s: %s and %s are identical (not copied)\n",
                                _PROGNAME, src, dest)
                os.Exit(1)
        }

        rfd, e = syscall.Open(src, syscall.O_RDONLY, 0)
        if rfd == -1 {
                fmt.Fprintf(os.Stderr, "%s: %s: %s\n",
                                _PROGNAME, src, syscall.Errstr(e))
                os.Exit(1)
        }
        defer syscall.Close(rfd)

        /* if dest is a directory, create the file under it */
        if (syscall.S_IFDIR & sb2.Mode) != 0 {
                dest = dest + "/" + path.Base(src)
        }

        wfd, e = syscall.Open(dest, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_TRUNC, 0666)
        if wfd == -1 {
                fmt.Fprintf(os.Stderr, "%s: %s: %s\n",
                                _PROGNAME, dest, syscall.Errstr(e))
                os.Exit(1)
        }
        defer syscall.Close(wfd)

        /* shortcut for zero-length source files */
        if (sb1.Size == 0) {
                return
        }

        var buf = make([]byte, sb1.Blksize)
        for {   
                r, e = syscall.Read(rfd, buf)
                if r < 0 {
                        fmt.Fprintf(os.Stderr, "%s: %s\n",
                                        _PROGNAME, syscall.Errstr(e))
                        os.Exit(1)
                }
                if r == 0 {
                        break
                }

                w, e2 := syscall.Write(wfd, buf[:r])
                if w != r || w == -1 {
                        fmt.Fprintf(os.Stderr, "%s: %s\n",
                                        _PROGNAME, syscall.Errstr(e2))
                        /* We differ from cp(1) here in that we abort on
                        * error; cp(1) warns and moves on to the next
                        * file.  This trivial version of cp only
                        * handles
                        * one file, so we can exit. */
                        os.Exit(1)
                }
        }
}

/* trivially copy a file to another file or into a directory */
func main() {

        if len(os.Args) != 3 {
                fmt.Fprintf(os.Stderr, "Usage: %s src dest\n", _PROGNAME)
                os.Exit(1)
        }

        argv := os.Args
        copy(argv[1], argv[2])
}

And there you have it, my first impression of the Go programming language. So far, I like it. Now, back to re-reading the tutorial sections on concurrency and goroutines...

What's with the title?

October 5, 2011


[Go Lisp!] [index] [It's a Book!]