Go Cheat Sheet for Perl Hackers
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.
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
Formatters and Tidiers
Perl
perlimports -i file.pl
perltidy -i file.pl
perlcritic file.pl
Go
goimports -w main.go
gofmt -w main.go
See also
Setting Up a New Developer Environment
Perl
cpm install -g App::perlimports Perl::Tidy Perl::Critic
This will install a perlimports, perltidy and perlcritic.
Go
go install golang.org/x/tools/cmd/goimports@latest
Setting Up a New CoderPad Environment
As of this writing, CoderPad's go version in the "Go Application" sandbox is
go1.22.2 linux/amd6
That means we cannot install the very latest version of goimports. We'll need to install an earlier tag.
go install golang.org/x/tools/cmd/goimports@v0.30.0
You can also install other helpful binaries, using ubi:
export PATH=~/bin:$PATH
mkdir -p ~/bin && curl --silent --location https://raw.githubusercontent.com/houseabsolute/ubi/master/bootstrap/bootstrap-ubi.sh | sh
ubi --project oalders/is --in ~/bin
is known os name --debug is known arch
Formatting Your CoderPad Code
CoderPad will format Go code on save. If you'd rather do it at the command line, try running:
Go
gofmt -w main.go && goimports -w main.go
Or just run goimports manually:
goimports -w main.go
Go Project
In a Go Project on CoderPad, you can run goimports on save. Just edit the .cpad file to something like this:
{
"targets": {
"run": {
"label": "Main",
"command": "goimports -w src && go run coderpad"
},
"test": {
"label": "Tests",
"command": "go test coderpad/echo"
}
}
}
Note that the changed line is targets.run.command, which was originally just go run coderpad.
The Language
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) */
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") }
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 := ""
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
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 string") }
var mySlice []int
if mySlice == nil { fmt.Println("nil") } if len(mySlice) == 0 { fmt.Println("empty slice") } }
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,
}
Go
A struct is a collection of fields.
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 = ( key1 => 'foo', key2 => '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 = ( key1 => 'foo', key2 => '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 = ( key1 => 'foo', key2 => '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
package main
import "fmt"
func main() { seen := 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 (length of array, which is 10 in this case) 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
Sleep
Perl
sleep 2;
Go
package main
import "time"
func main() { time.Sleep(2 * time.Second) }
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) } }
Note: litter has some global configuration values which you may find helpful. For instance, by default it hides private fields, but this can be disabled:
litter.Config.HidePrivateFields = false
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.OAPPEND|os.OCREATE|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")) }
See also https://pkg.go.dev/time#pkg-constants
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
To test a subset of functions:
$ go test -run regexp
To bypass Go's test caching:
$ go test -count=1
Test Coverage
To generate a coverage report:
go test -v ./... -coverprofile cover.out && go tool cover -html cover.out -o cover.html
At this point you can view cover.html in a web browser and get a nice visual of your test coverage.
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)