Home

Awesome

<!-- vim-markdown-toc GFM --> <!-- vim-markdown-toc -->

go-for-perl-hackers

The primary aim of this cheat sheet is to help Perl programmers get up and running with Go.

Your editor

I recommend setting up gopls.

Go vs Perl

Installing binaries:

Perl

cpm install -g App::perlimports

This will install a perlimports binary in your $PATH.

Go

go install golang.org/x/tools/cmd/goimports@latest

This will install goimports into $GOPATH/bin

Constructs

In this section we'll document some commonly used Perl constructs and try to find their equivalents in Go.

Comments

Perl

# single line

=pod

Multi line

=cut

Go

// single line (C++-style)

/*
Multi-line (C-Style)
*/

Print

Printing strings

Perl
print 'hello, world';
Go
package main

import "fmt"

func main() {
	fmt.Print("hello, world")
}

See "Double vs Single Quotes" for more information on why single quotes are not used to quote strings in Go.

Formatted print statements.

Perl
printf('We are open %i days per %s', 7, 'week');
Go
package main

import (
	"fmt"
)

func main() {
	fmt.Printf("We are open %d days per %s", 7, "week")
}

See golang.org/pkg/fmt/

Printing from within a test

Perl
diag 'foo happens';
Go
t.Log("foo happens")
t.Logf("We are open %d days per %s", 7, "week")

Variables

Environment Variables

Perl
$ENV{FOO} = 'bar';
local $ENV{FOO} = 'bar'; # Same as above, but with local scope

print "GOPATH: $ENV{GOPATH}\n";
Go
os.Setenv("FOO", "bar")

fmt.Println("GOPATH: ", os.Getenv("GOPATH"))

Variable Assignment

Perl
my $foo = 'bar';
my $pi = 3.14;
my $no_assignment;
Go
// the following assignments are equivalent
var foo = "bar"
foo := "bar"

var pi float32 = 3.14 // explicit cast as float32
pi := float32(3.14)   // explicit cast as float32
pi := 3.14            // implicit cast as float64
pi := "3.14"          // implicit cast as string

var noAssignment string // equivalent to: noAssignment := ""

https://go.dev/tour/basics/11:

When you need an integer value you should use int unless you have a specific reason to use a sized or unsigned integer type.

Multiple Variables

Declare without explicit values
Perl
my ($foo, $bar);
Go
var foo, bar int
var nothing []string // create an empty slice
Declare with explicit values
Perl
my ($i, $j) = (1, 2);
Go
var i, j int = 1, 2

Double vs Single Quotes

Perl
my $foo = 'bar';      # no variable interpolation
my $bar = "$foo baz"; # allow for variable interpolation
Go
foo := "本" // implicitly cast as a string
foo := '本' // implicitly cast as a rune

Rune is an alias for int32 and represents a Unicode code point.

For example: fmt.Println('a') prints 97, which is hexadecimal 61. The Unicode for a is U+0061.

To print the Hex value of 'a' you can also do fmt.Println("%x", 'a')

Go expects only a single letter inside single quotes, so this will yield a compile time error:

fmt.Printf("%s", 'hello')

more than one character in rune literal

See golang.org/ref/spec#Rune_literals and https://go.dev/tour/basics/11

Multiline strings

Perl
my $long_string = <<'EOF';
my multiline
string
EOF

Use double quotes <<"EOF"; if you need to interpolate variables.

Go
longString := `
my multiline
string
`

Boolean checks (true/false)

Perl
my $success = 1;    # true
$success = 'foo';   # true
$success = 0;       # false
$success;           # (undef) false

if ($success) {
    print "This succeeded";
}

if ( !$success ) {
    print "This failed";
}

Go
var success bool

success = true
success = false

if success == true {
	fmt.Println("This succeeded")
}

if success {
	fmt.Println("This succeeded")
}

if success == false {
	fmt.Println("This failed")
}

if !success {
	fmt.Println("This failed")
}

Checking for (un)definedness

Perl
my $foo;
if ( ! defined $foo ) {
    ...;
}
Go
package main

import "fmt"

func main() {
	var myString string

	if myString == "" {
		fmt.Println("Empty")
	}

	var mySlice []int

	if mySlice == nil {
		fmt.Println("nil")
	}
	if len(mySlice) == 0 {
		fmt.Println("empty")
	}
}

Go Playground

Incrementing and Decrementing Integer

Perl

See https://perldoc.perl.org/perlop.html#Auto-increment-and-Auto-decrement

$i = 0;  $j = 0;
print $i++;  # prints 0
print ++$j;  # prints 1

$i = 0;  $j = 0;
print $i--;  # prints 0
print --$j;  # prints -1
Go
counter := 1
counter++
counter--

Division with Integers

Perl
my $amount = 18 / 20_000;
Go
package main

import "fmt"

func main() {
	amount := float32(18) / float32(20000)
	fmt.Println(amount)
}

Note: without the float32 conversion, amount will be 0.

https://go.dev/play/p/ZIotxD2kQen

String Concatenation

Perl
my $foo = 'go';
my $bar = 'pher';

my $gopher = "$foo$bar";
$gopher = $foo . $bar;
$gopher = join q{}, $foo, $bar;
$gopher = sprintf '%s%s', $foo, $bar;
Go
package main

import (
	"fmt"
	"strings"
)

func main() {
	var gopher string

	gopher = foo + bar
	gopher = fmt.Sprintf("%s%s", foo, bar)
}
package main

import (
	"fmt"
	"strings"
)

func main() {
	foo := "go"
	bar := "pher"

	gopher = strings.Join([]string{"go", "pher"}, "")
	fmt.Println(gopher)
}
package main

import (
	"bytes"
	"fmt"
)

func main() {
	var buffer bytes.Buffer
	foo := "go"
	bar := "pher"

	buffer.WriteString(foo)
	buffer.WriteString(bar)
	fmt.Println(buffer.String())
}

String Concatenation (Existing String)

Perl:
my $foo = 'go';
$foo .= 'pher';
Go:
package main

func main() {
	foo := "go"
	foo += "pher"
}

https://go.dev/play/p/UJqGTHWCbQB

string to byte array Conversion

Go
b := []byte("Each day provides its own gifts")

byte array to string Conversion

Go
package main

import "fmt"

func main() {
	// create the byte array
	b := []byte("Each day provides its own gifts")

	// convert back to a string by passing the array
	contentAsString := string(b)
	fmt.Println(contentAsString)

	// convert back to a string by passing a slice referencing the storage of b
	contentAsString = string(b[:])
	fmt.Println(contentAsString)
}

https://go.dev/play/p/DKsQJmS5yuf

Constants

Perl
use Const::Fast;
const my $hello => 'Hello, world';
Go
// Create an *untyped* string constant
const hello = "Hello, world"

// Create a typed string constant
const hello string = "Hello, World"

Create multiple constants with one const declaration:

const (
	hello   = "Hello, world"
	goodbye = "Goodbye!"
)

Constants cannot be declared using the := syntax.

Lists

Create an Array

Perl
my @foo = (1..3);
my $first = $foo[0];
Go
foo := [3]int{1, 2, 3}
first := foo[0]

Note that creating an empty array in Go means that it will be populated by the type's default values:

var bar [5]int // creates an array of [0,0,0,0,0]

Size of an array:

Perl
my $size = @array;
Go
size := len(array)

Hashes / Structs

Perl
use Data::Printer; # exports p()

my %foo = (
    X => 1,
    Y => 2,
);

$foo{X} = 4;
print $foo{X}; # prints 4

p %foo;
# prints:
# {
#    X => 4,
#    Y => 2,
# }

delete $foo{X};
Go

https://play.golang.org/p/wyeohYSw-cf

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	v.X = 4
	fmt.Println(v.X)       // prints 4
	fmt.Printf("%+v\n", v) // prints {X:4 Y:2}

	// additional examples
	v1 := Vertex{1, 2} // has type Vertex
	v2 := Vertex{X: 1} // Y:0 is implicit
	v3 := Vertex{}     // X:0 and Y:0

	v.X = 0
	fmt.Printf("1: %d, 2: %d, 3: %d", v1.X, v2.X, v3.X)
}

Iterating Over a List

Perl
my @foo = ('foo', 'bar', 'baz');

for my $value ( @foo ) {
    print "$value\n";
}
my @foo = ('foo', 'bar', 'baz');

for (@foo) {
    print "$_\n";
}
// Print array index for each element
my @foo = ('foo', 'bar', 'baz');

for my $i (0..$#foo) {
    print "$i: $foo[$i]\n";
}

Go
package main

import (
	"fmt"
)

func main() {
	foo := [3]int{1, 2, 3}

	for i, v := range foo {
		fmt.Printf("index: %v value: %v\n", i, v)
	}
}

Splitting a string

Perl
my @list = split ',', 'a,b,c'
Go

https://play.golang.org/p/RXUdiKhZsjG

package main

import (
	"fmt"
	"strings"
)

func main() {
	mySlice := strings.Split("a,b,c", ",")
	fmt.Printf("%v", mySlice)
}

Iterating Over a Hash/Map

Perl using keys
my %hash = ( key_1 => 'foo', key_2 => 'bar', );
for my $key ( keys %hash ) {
    printf( "key: %s value: %s\n", $key, $hash{$key} );
}

Go using only primary return value
package main

import (
	"fmt"
)

func main() {
	myMap := map[string]string{"key1": "foo", "key2": "bar"}

	for k := range myMap {
		fmt.Printf("key: %s value: %s\n", k, myMap[k])
	}
}
Perl using each
my %hash = ( key_1 => 'foo', key_2 => 'bar', );
for my $key, $value ( each %hash ) {
    printf( "key: %s value: %s\n", $key, $value );
}

Go using both primary and secondary return values
package main

import (
	"fmt"
)

func main() {
	myMap := map[string]string{"key1": "foo", "key2": "bar"}

	for k, v := range myMap {
		fmt.Printf("key: %s value: %s\n", k, v)
	}
}
Perl: using values
my %hash = ( key_1 => 'foo', key_2 => 'bar', );
for my $value ( values %hash ) {
    printf( "value: %s\n", $value );
}
Go: ignoring primary return value, using only secondary return value
package main

import (
	"fmt"
)

func main() {
	myMap := map[string]string{"key1": "foo", "key2": "bar"}

	for _, v := range myMap {
		fmt.Printf("value: %s\n", v)
	}
}

Checking if a Hash/Map Key Exists

Perl

my %pages = ( home => 'https://metacpan.org' );
if ( exists $foo{home} ) {
    ...
}

Go

https://stackoverflow.com/a/2050629/406224

package main

import "fmt"

func main() {
	pages := make(map[string]string)
	pages["home"] = "https://metacpan.org"
	if _, ok := pages["home"]; ok {
		fmt.Println("ok")
	}
}

Using a Hash/Map to Track Seen Things

Perl
my %seen;
$seen{sunrise} = 1;
if ( exists $seen{sunrise} ) {
   ...
}
Go

https://go.dev/play/p/d1RTCE1pmaH

package main

import "fmt"

func main() {
	seen := make(map[string]struct{})
	seen["sunrise"] = struct{}{}
	if _, ok := seen["sunrise"]; ok {
		fmt.Println("I have seen the sunrise")
	}
}

The motivation here is that if the value has no inherent meaning, it could be confusing to assign it something which could convey a meaning, like true or false. There may be a small performance gain in terms of memory when using struct{}, but it may not be any faster. https://gist.github.com/davecheney/3be245c92b61e5045f75

Deleting a Hash/Map Key

Perl

my %pages = ( home => 'https://metacpan.org' );
delete $pages{home};
Go
package main

import "fmt"

func main() {
	pages := make(map[string]string)
	pages["home"] = "https://metacpan.org"
	delete(pages, "home")
}

Getting a List of Hash/Map Keys

Perl

my %pages = ( home => 'https://metacpan.org' );
my @keys = keys %pages;
Go

https://go.dev/play/p/Z8OlHN7Q3GY

package main

import (
	"fmt"
)

func main() {
	pages := make(map[string]string)
	pages["home"] = "https://metacpan.org"

	var keys []string
	for k := range pages {
		keys = append(keys, k)
	}
	fmt.Printf("%+v", keys)
}

Slices:

Perl
my @array = (0..5);
my @slice = @list[2..4];
Go
array := [6]int{0, 1, 2, 3, 4, 5}
var slice []int = array[2:4]

var myslice []int    // create an empty slice of integers
var nothing []string // create an empty slice of strings

Note that arrays in Go have a fixed size, whereas slices are dynamically sized.

Also:

A slice does not store any data, it just describes a section of an underlying array.

Changing the elements of a slice modifies the corresponding elements of its underlying array.

Other slices that share the same underlying array will see those changes.

See https://tour.golang.org/moretypes/8

Note also that slices in Go can use defaults for lower and upper bounds. That means that for the array of 10 integers var a [10]int, the following slices are equivalent:

var a [10]int

a[0:10] // explicit lower to upper bound
a[:10]  // use default lower bound (0)
a[0:]   // use default upper bound (0)
a[:]    // use default upper and lower bounds (0 and 10)

Note that the lower bound is the starting point in the index (ie 0) and the length of the slice is the upper bound, which is why the entire slice consists of a[0:10 and not a[0:9].

See https://tour.golang.org/moretypes/10

Appending Slices:

Perl
my @array = (0..5);
my @slice = @list[2..4];
push @slice, 11, 12;

Note that in Perl, a slice of an array is also an array, so there's no need to make a distinction between the two.

Go
array := [6]int{0, 1, 2, 3, 4, 5}
var slice []int = array[2:4]
slice = append(slice, 11, 12)

In Go, if you want to add two slices together, you'll need to treat the second slice as a variadic parameter (...).

package main

import "fmt"

func main() {
	first := []int{1, 2}
	second := []int{3, 4}
	total := append(first, second...)
	fmt.Printf("%+v", total)
}

See https://go.dev/play/p/ylbpv1KDjzE

Dumping Data Structures

To your terminal

Perl
use strict;
use warnings;
use feature qw( say );

use Data::Printer; # exports p() and np()

my %foo = ( a => 'b' );
p( %foo );

# or

say np( %foo );
Go
package main

import "fmt"

func main() {
	var config struct {
		user string
		pass string
	}

	config.user = "florence"
	config.pass = "machine"

	fmt.Printf("%+v", config)

	return
}

Or:

package main

import "github.com/sanity-io/litter"

func main() {
	var config struct {
		user string
		pass string
	}

	config.user = "florence"
	config.pass = "machine"

	litter.Dump(config)

	return
}

Or:

package main

import (
	"encoding/json"
	"fmt"
	"log"
)

func main() {
	data := []string{"one", "two", "three"}
	fmt.Println(prettyJSON(data))
}

func prettyJSON(target any) string {
	data, err := json.MarshalIndent(target, "", "    ")
	if err != nil {
		log.Fatalf("Cannot create pretty json from %v: %v", target, err)
	}
	return string(data)
}

https://play.golang.org/p/FbSfHRNoVfP

To disk (write)

Perl
use Data::Printer; # exports np()
use Path::Tiny qw(path);

my @list = ( 1..3 );
path('/tmp/foo.txt')->spew( np( @list ) );
Go
package main

import (
	"log"
	"os"

	"github.com/sanity-io/litter"
)

func main() {
	list := [3]int{1, 2, 3}
	err := os.WriteFile("/tmp/foo.txt", []byte(litter.Sdump(list)), 0666)
	if err != nil {
		log.Fatal(err)
	}
}

Go Playground

To disk (append)

Perl
use Data::Printer; # exports np()
use Path::Tiny qw(path);

my @list = ( 1..3 );
path('/tmp/foo.txt')->append( np( @list ) );
Go
package main

import (
	"log"
	"os"

	"github.com/davecgh/go-spew/spew"
)

func main() {
	list := [3]int{1, 2, 3}

	file, err := os.OpenFile(
		"/tmp/foo.txt",
		os.O_APPEND|os.O_CREATE|os.O_WRONLY,
		0666,
	)

	if err != nil {
		log.Fatal(err)
	}

	spew.Fdump(file, list)
	file.Close()
}

File Operations

Creating a directory

Perl
use Path::Tiny qw( path );
use Try::Tiny qw( catch try );

my $dir = '.my-perl-cache';
try {
    path($dir)->mkpath( { chmod => 0644 });
}
catch {
    die sprintf "Could not create dir %s because of %s", $dir, $_;
};
Go
package main

import (
	"log"
	"os"
)

func main() {
	cacheDir := ".my-go-cache"
	if err := os.MkdirAll(cacheDir, 0644); err != nil {
		log.Printf("Cannot create dir %s because %v", cacheDir, err)
	}
}

Read an Entire File

Perl
use Path::Tiny qw( path );

my $content = path('path', 'to', 'file')->slurp_utf8;
Go

Note that in this case dat is []byte

package main

import (
	"fmt"
	"log"
	"os"
)

func main() {
	err := os.WriteFile("test.txt", []byte("Hello, Gophers!"), 0666)
	if err != nil {
		log.Fatal(err)
	}

	dat, err := os.ReadFile("test.txt")
	if err != nil {
		log.Fatal(err)
	} else {
		fmt.Println(string(dat))
	}
}

https://go.dev/play/p/3dCR2NJuZOF

Read First Line of a File

Perl

In the Perl example, we'll chomp the line to make explicit that newlines need to be handled.

use Path::Tiny qw( path );

my $first_line;
my $fh = path('/path/to/file')->openr_utf8;

while (my $line = <$fh>) {
    $first_line = $line;
    chomp $first_line;
    last;
}

print $first_line, "\n";
Go

scanner.Scan() helpfully trims newlines for us.

import (
	"fmt"
	"log"
)

func main() {
	file, err := os.Open("/path/to/file")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)

	var firstLine string
	for scanner.Scan() {
		firstLine = scanner.Text()
		break
	}
	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}

	fmt.Println(firstLine)
}

Flow Control

if

Perl
if ( $foo > 1 ) {
    print 'bar';
}
Go
if foo > 1 {
	fmt.Println("bar")
}

// parens are optional
if foo > 1 {
	fmt.Println("bar")
}

else

Perl
if ( $foo > 1 ) {
    print 'bar';
}
else {
    print 'baz';
}
Go
if foo > 1 {
	fmt.Println("bar")
} else {
	fmt.Println("baz")
}

elsif / else if

Perl
if ( $foo > 1 ) {
    print 'bar';
}
elsif ( $foo < 10 ) {
    print 'baz';
}
Go
if foo > 1 {
	fmt.Println("bar")
} else if foo < 10 {
	fmt.Println("baz")
}

Loops

For loops

Perl
my $sum;
for ( my $i = 0 ; $i < 10 ; $i++ ) {
    $sum += $i;
}
Go
sum := 0
for i := 0; i < 10; i++ {
	sum += i
}

While loops

Perl
my $sum = 0;
my $i = 0;
while ( $i < 10 ) {
    $sum += $i++;
}
Go
// The init and post statement in a Go for loop are optional.
sum := 0
i := 0
for i < 10 {
	sum += i
	i++
}

Infinite loops

Perl
while (1) {
}
Go
for {
}

Short-circuiting a loop iteration

Perl

while (1) {
    ...
    next if $foo eq 'bar';
    ...
}

Go

for {
	if foo == "bar" {
		continue
	}
	// Won't get here if continue is called
}

Note that continue will immediately begin the next iteration of the innermost for loop.

Terminating a loop

Perl

while (1) {
    ...
    last if $foo eq 'bar';
    ...
}

Go

for {
	if foo == "bar" {
		break
	}
	// Won't get here if break is called
}

Note that break will exit the enclosing loop at the point where it is called.

Regular Expressions

Match a string prefix.

Perl

my $str = '5.5.1';
if ( $str =~ m{\A5\.5} ) {
    print "ok\n";
}

Go

https://go.dev/play/p/rpACvjzRt-K

package main

import (
	"fmt"
	"regexp"
)

func main() {
	re := regexp.MustCompile(`\A5\.5`)
	str := "5.5.1"
	if re.MatchString(str) {
		fmt.Println("ok")
	}
}

https://go.dev/play/p/-NXI8SjMjJQ

package main

import (
	"fmt"
	"strings"
)

func main() {
	str := "5.5.1"
	if strings.HasPrefix(str, "5.5") {
		fmt.Println("ok")
	}
}

Today's Date as YYYY-MM-DD

Perl

use DateTime ();
print DateTime->now->ymd;

Go

https://play.golang.org/p/k0ijssDDOU6

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println(time.Now().Format("2006-01-02"))
}

Functions

Functions without signatures

Perl
sub foo {
    print 'ok';
}
foo();
Go
package main

import "fmt"

func main() {
	foo()
}
func foo() {
	fmt.Println("foo")
}

Running Tests

Perl

$ perl Makefile.PL
$ make
$ make test

or

$ prove -l t/path/to/test.t

Use the -v flag for verbose output:

$ prove -lv t/path/to/test.t

Go

$ go test

Use the -v flag for verbose output:

$ go test -v

If you're using vim-go, use :GoTest either from your foo.go or foo_test.go. (Note, you can also use :GoAlternate to toggle between the two files.)

To test a subset of functions:

$ go test -run regexp

If you're using vim-go, move your cursor to the name of the function you'd like to test. Running :GoTest here will run the function you're currently in.

To bypass Go's test caching:

$ go test -count=1

Debugging

Printing Stack Traces

Perl
use Carp qw( longmess );
print longmess();
Go
package main

import (
	"runtime/debug"
)

func main() {
	debug.PrintStack()
}

Sleep

Perl

sleep 60;

Go

package main

import (
	"time"
)

func main() {
	time.Sleep(60 * time.Second)
}

Parsing URIs

Perl

use Mojo::URL ();
my $url = Mojo::URL->new('https://www.google.com/search?q=schitt%27s+creek');
print $url->query->param('q'); # schitt's creek

Go

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	url, err := url.Parse("https://www.google.com/search?q=schitt%27s+creek")
	if err != nil {
		log.Fatal(err)
	}
	q := url.Query()
	fmt.Println(q.Get("q")) // schitt's creek
}

Changing URI Query Params

Go

package main

import (
	"fmt"
	"net/url"
)

func main() {
	url, _ := url.Parse("https://example.com")

	q := url.Query()
	q.Set("activity", "dance")
	q.Set("type", "flash")
	url.RawQuery = q.Encode()

	fmt.Println(url)
}

Command Line Scripts

Print first argument to a script

Perl
print $ARGV[0], "\n" if $ARGV[0];
Go
package main

import (
	"fmt"
	"os"
)

func main() {
	if len(os.Args) > 1 {
		fmt.Printf("%v\n", os.Args[1])
	}
}

Exiting a script

Perl
exit(0);
Go
import("os")

os.Exit(0)