I’m not a heavy programmer. I started in on it back in the 80’s and made a split career of programming and building systems in the 90’s, but by the mid 2000’s I went off into Systems Engineering and focused on “programming” large scale virtual server environments. Most of my job revolves around writing code to automate the monitoring and upkeep of these systems. For this level of work interpreted languages like Perl, Python and Ruby work just fine. I also work with developers in helping them deploy their code into production and again for most of what they do the “easy” languages of Perl, Python, Ruby and PHP work very well. I’d also place Java in that lineup, for while it compiles down to bytecode the JVM is really just another interpreter and the language is a far cry from the heavyweights of C and C++ that compile down into native code. The Java, Ruby, Python, Perl and PHP kids are all eating at the same table in the cafeteria while the C and C++ guys sit with their long beards in the corner arranging their peas into organized rows before eating.
One big problem with interpreted languages is deployment. A prime example is one tool I’m dependent on for day to day work, Puppet. Puppet is a program that runs on each server and checks into a master server. It downloads scripts from the master server and runs them. This let’s a sysadmin install software, add users, create cron jobs and a lot of other things across hundreds of servers simply by changing a single config file on the master server. It’s an invaluable tool and is a good use example of software written in Ruby. It doesn’t have to run quickly, it doesn’t have to have a small footprint, it just needs to work well. Which is does, many companies use it including Google.
But it’s also a good example of what’s bad about languages like Ruby. If your OS is say, CentOS 5, it may ship with Ruby version 1.8.5. This version of Ruby causes a problem with Puppet which ends up with the program gobbling up ram. So you need to upgrade to 1.8.6 which requires adding the EPEL repository for CentOS/Red Hat. On the other end of that, the newest version of Ruby is 1.9 and soon you can expect distributions to ship with it. Only Puppet doesn’t work with that version of Ruby yet. So in order to use Puppet you need this narrow window of Ruby which may or may not be easy to install on your platform of choice.
Pretty much all of the interpreted languages suffer from this. Java can break if you’re not using the right JVM. Python and Perl are pretty safe with the core interpreter, but any software you run that uses either will depend on outside libraries. PHP is the same way, depending on php-mysql, PECL, or any number of extra library addons.
Enter Go. Go is a new language released by Google that has its foundation in C but is modernized. In use it feels a lot like the newer interpreted languages, but it compiles down to native code like C and C++ do. It comes with a rich standard library which has been the foundation for people writing new libraries for MySQL, SSH, SNMP, web frameworks and so on. The language works very well with threading, is strongly typed but doesn’t require you to be wordy with it, is garbage collected, compiles quickly, and statically links all compiled binaries. And it’s that last bit which makes the language really interesting.
Here’s your standard Hello World program:
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
Building and running it looks like this:
$ go build hello.go
$ ./hello
Hello World
I can take that compiled hello program and run it on any 64bit Linux machine. I can even compile it for 32bit platforms on my 64bit machine if I wanted to. It doesn’t depend on any outside library. No, really:
ldd hello
not a dynamic executable
But let’s get a little more complicated. Let’s write a program that queries a remote database using a 3rd party MySQL library written in Go: https://github.com/ziutek/mymysql
package main
import (
"os"
"github.com/ziutek/mymysql/mysql"
_ "github.com/ziutek/mymysql/native" // Native engine
)
func main() {
db := mysql.New("tcp", "", "192.168.177.225:3306", "testuser", "TestPasswd9", "test")
err := db.Connect()
if err != nil {
panic(err)
}
rows, _, err := db.Query("select * from a where id > %d", 20)
if err != nil {
panic(err)
}
for _, row := range rows {
for _, col := range row {
if col == nil {
// col has NULL value
} else {
// Do something with text in col (type []byte)
}
}
// You can get specific value from a row
val1 := row[1].([]byte)
// You can use it directly if conversion isn't needed
os.Stdout.Write(val1)
}
}
So here this program is using the MyMySQL library to connect to a remote database on 192.168.177.225 and run a select query. Pretty standard stuff and it builds and runs like:
$go build mysqltest.go
$./mysqltest
4042
Like I said, pretty standard stuff. Let’s look what this software depends on:
$ objdump -p mysqltest | grep NEEDED | awk '{print $2}'
libpthread.so.0
libc.so.6
I basically just need 2 standard core C libraries to run this app. I don’t even think Linux will boot without these libraries. I don’t need MySQL libraries at all to run my MySQL program. As an example I can throw this app on another machine without the MySQL C libraries installed:
# rpm -qa | grep -i mysql
# ldconfig -v | grep -i mysql
ldconfig: /etc/ld.so.conf.d/kernelcap-2.6.18-194.26.1.el5.conf:6: duplicate hwcap 0 nosegneg
ldconfig: /etc/ld.so.conf.d/kernelcap-2.6.18-238.9.1.el5.conf:6: duplicate hwcap 0 nosegneg
ldconfig: /etc/ld.so.conf.d/kernelcap-2.6.18-238.el5.conf:6: duplicate hwcap 0 nosegneg
# ./mysqltest
4042
Keep in mind that I can’t even run a C program that uses MySQL without libmysqlclient.so being installed on the system. But Go doesn’t care.
This is where I think Go really becomes interesting and wins out against other languages. Imagine deployments where all you care about is 32bit or 64bit. You don’t need libs on your target machine, you don’t need complicated deployment systems that make sure a target machine has X, Y and Z. You simply build a binary and can push it out to anything built in the last decade.
Go brings along a lot of other advantages if you’re coming from an interpreted background. Compiled code is always going to be faster than interpreted(though at the moment Go’s speed is still being worked on) and it has extremely good threading support, something which a lot of interpreted languages are pretty bad with. On the down side it does away with certain concepts in the name of simplicity and code readability(generic types and polymorphism being two examples) and while it has a pretty solid core library it still lacks the decade of tools and libs that other languages have built up.