Go error handling is even worse than sh [Sunday, 2012-10-28]
While writing zwm I started using xgb code to understand how the protocol works, because the specs are practically useless.
The first thing I did with it was writing the code to read the authority files to get the name and the data for the authentication process.
This is the code it uses to get that data:
// readAuthority reads the X authority file for the DISPLAY.
// If hostname == "" or hostname == "localhost",
// then use the system's hostname (as returned by os.Hostname) instead.
func readAuthority(hostname, display string) (
name string, data []byte, err error) {
// b is a scratch buffer to use and should be at least 256 bytes long
// (i.e. it should be able to hold a hostname).
b := make([]byte, 256)
// As per /usr/include/X11/Xauth.h.
const familyLocal = 256
if len(hostname) == 0 || hostname == "localhost" {
hostname, err = os.Hostname()
if err != nil {
return "", nil, err
}
}
fname := os.Getenv("XAUTHORITY")
if len(fname) == 0 {
home := os.Getenv("HOME")
if len(home) == 0 {
err = errors.New("Xauthority not found: $XAUTHORITY, $HOME not set")
return "", nil, err
}
fname = home + "/.Xauthority"
}
r, err := os.Open(fname)
if err != nil {
return "", nil, err
}
defer r.Close()
for {
var family uint16
if err := binary.Read(r, binary.BigEndian, &family); err != nil {
return "", nil, err
}
addr, err := getString(r, b)
if err != nil {
return "", nil, err
}
disp, err := getString(r, b)
if err != nil {
return "", nil, err
}
name0, err := getString(r, b)
if err != nil {
return "", nil, err
}
data0, err := getBytes(r, b)
if err != nil {
return "", nil, err
}
if family == familyLocal && addr == hostname && disp == display {
return name0, data0, nil
}
}
panic("unreachable")
}
func getBytes(r io.Reader, b []byte) ([]byte, error) {
var n uint16
if err := binary.Read(r, binary.BigEndian, &n); err != nil {
return nil, err
} else if n > uint16(len(b)) {
return nil, errors.New("bytes too long for buffer")
}
if _, err := io.ReadFull(r, b[0:n]); err != nil {
return nil, err
}
return b[0:n], nil
}
func getString(r io.Reader, b []byte) (string, error) {
b, err := getBytes(r, b)
if err != nil {
return "", err
}
return string(b), nil
}
This is my translation to Zsh:
function X:auth:read-short {
integer fd="$2"
local encoded
sysread -i $fd -s 2 encoded || return $?
local first="$encoded[1]"
local second="$encoded[2]"
typeset -g "${1:-REPLY}=$(( #first << 8 | #second ))"
}
function X:auth:read-string {
integer fd="$2"
integer length
X:auth:read-short length "$fd"
sysread -i $fd -s $length $1 || return $?
}
function X:auth:read {
local display="$2"
local hostname="${3:-$HOST}"
local authority="${XAUTHORITY:-$HOME/.Xauthority}"
integer fd
exec {fd}<"$authority"
if [[ -z $fd ]]; then
return 1
fi
integer family
local addr
local disp
local name
local data
while true; do
X:auth:read-short family $fd || break
X:auth:read-string addr $fd || break
X:auth:read-string disp $fd || break
X:auth:read-string name $fd || break
X:auth:read-string data $fd || break
if [[ "$family" == 256 && "$addr" == "$hostname" && "$disp" == "$display" ]]
then
set -A "${1:-reply}" name "$name" data "$data"
break
fi
done
exec {fd}>&-
(( ${#${(P)1:-reply}} ))
}
Error handling is easier and better looking in shell scripts than with Go, most of the code in the loop in the Go code is error checking and returning the same thing.
Now that could have been easily rewritten like I did my thing, but the way he did it is idiomatic Go, with exceptions that would have been way easier on the eyes and easier to understand too.
But who am I to talk badly about Go? I’m the one writing a window manager in Zsh.