- Project Folder Org vs Module Layout
type T testing.T
is a new type (which is never equal to the base.)type T = testing.T
is an alias. https://go.dev/blog/alias-names- Printf verbs
- Just like Java, you can't design-out the opportunity to mess it up. Fools always find a way.
Surprisingly, our study shows that it is as easy to make concurrency bugs with message passing as with shared memory, sometimes even more. -- Understanding Real-World Concurrency Bugs in Go
Importing libraries, with and without a collision-preventing alias
Go, singular import of a full libraries
package main
import "fmt"
func main() {
fmt.Println("Hello printf")
}
JS, CJS, any variant of full-library
const anyname = require('util');
JS, ESM, any variant of full-library
import util from 'node:util';
Go, multiple libraries
package main
import (
"fmt"
"time"
"math"
"math/rand" // convention is: the files providing that say `package rand`
)
// (individual import statements are legal but not recommended)
func main() {
fmt.Println("Hello", time.Now(), math.Pi, math.Sqrt(4), rand.Intn(10))
}
Go aliasing a naming conflict
https://go.dev/ref/spec#Import_declarations
import (
localname "package_name"
)
Exporting a function
Go exports Capitalized functions
package lib
func Thisexports() {
}
func this_doesnt() {
}
Js, CJS
module.exports = {f};
function f() {}
Js, ESM
export function f() {}
Parameter Types
Go is like C-reversed
https://go.dev/blog/declaration-syntax
func f(a int, b int) int {
return a+b
}
For a series of the same type, you can omit all but the last:
func point3d(x, y, z int) {
}
Tuples are valid types:
func indecisive(a string, b string) (string, string) {
return (
fmt.Sprint("I like ", a),
fmt.Sprint("but I also like ", b),
)
}
func main() {
a, b := indecisive("cats", "dogs")
}
(fmt.Sprint() adds spaces if neither adjacent term is a string)
Js has only universal refs
function f(a, b) {
return a+b;
}
const fatarrow_statement_body = (a, b) => {return a+b;};
const fatarrow_expression_body = (a, b) => a+b;
Ts is like Go but with a colon
https://www.typescriptlang.org/docs/handbook/2/functions.html
function f(a: number, b: number) {
return a+b;
}
For functions as parameters, inline gets unreadable quickly:
function b(adder: (a: number, b: number) => number) {
return adder(1,2);
}
But type aliases are nice:
type BinaryOpFunc = (a: number, b: number) => number;
function b(adder: BinaryOpFunc) {}
Return statements
Go likes naming returns at the top
If you put a name on your return signature elements, you can make a naked return statement return those locals:
func returnsTuple() (x, y int) {
x = 12
y = 42
return // "(x, y)" is implied
}
func main() {
x, y := returnsTuple()
}
Js and everyone else wouldn't
function returnsArray() {
return [12, 42];
}
function main() {
const [a,b] = returnsArray();
}
Returns are not Tuples
I've been reading return a,err
as if the func returns a tuple, but that's not exactly true. If it were a tuple, it'd be one lvalue or parameter when consumed, but here, the call to CommaErr() unpacks the sequence as two actual params:
package main
import "fmt"
func main() {
TestCheck()
TestCommaErr()
}
func Check(err error) {
if err != nil {
panic(err)
}
}
func CommaErr[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
func Parse(str string) (i int, err error) {
if str == "42" {
i = 42
} else {
err = fmt.Errorf("cannot parse %v", i)
}
return
}
func Ping() error {
return fmt.Errorf("nothing to ping")
}
func recovery() {
if err := recover(); err != nil {
fmt.Println("recover said", err)
}
}
func TestCheck() {
defer recovery()
Check(Ping())
}
func TestCommaErr() {
defer recovery()
fmt.Println("test42", CommaErr(Parse("42"))) // passes; CommaErr returns int(42)
fmt.Println("test20", CommaErr(Parse("20"))) // works as designed: CommaErr panics with Parse's error.
}
Variable Declarations
Go uses var and a separate type
func main() {
var name string // without an initial value, decl must have a type
var job = "pope"
job := "pope" // _inside a function but not at top scope,_ pascal-assign is shorthand for "var {name}={val}"
var x,y,z int; // just like in signatures, repetitive types can be collapsed.
var x,y,z int = 1,2,3; // Go list-decls have a typle-based initializer syntax
const a = 42; // sounds limited to char, string, bool, and numbers? really?
// consts are compile-type _values_ without a firm type, so this works without a cast:
b int8 := a
c float32 := a
}
Js only uses runtime type info
function main() {
var old_fashioned_let; // don't use var. var has peculiar scoping rules and should be left in the trashbin with the other early-century rubbish.
const this = "is a constant pointer/ref to unconstant data";
let normal_var;
const x=1, y=2, z=3; // JS initializer syntax are based on comma delimited expressions.
}
Fundamental Types
Js | Go |
---|---|
string | string |
boolean | bool |
number | int† int8 int16 int32 (alias: "rune" for unicode reasons) int64 uint† uint8 (alias: byte) uint16 uint32 uint64 uintptr† float32 float64 |
BigInt | |
complex64 complex128 | |
null | |
undefined | |
symbol |
- † Like C/C++, the width of int, uint, and uintptr are architecture-specific. So don't use them for portable numbers; they are for tasks that are supposed to be locked to machine width.
- Go decls without an initializer are initialized to "zeroish": empty for string, false for bool, nil for pointers, zero for everything else.
- Js refs initialize to undefined.
- Casting: Go is like C++'s explicit casts:
i:=42; var f float = float(i)
Example of complex128:
import (
"fmt"
"math/cmplx"
)
var z complex128 = cmplx.Sqrt(-5 + 12i)
func main() {
fmt.Printf("Type: %T Value: %v\n", z, z)
}
result: Type: complex128 Value: (2+3i)
Strings
Interpolation
see package "text/template"
https://go.dev/blog/stringshttps://www.reddit.com/r/golang/comments/153g5k6/easier_string_interpolation/https://github.com/golang/go/issues/34174https://github.com/golang/go/issues/34174#issuecomment-1450932232https://stackoverflow.com/questions/53879154/println-vs-printf-vs-print-in-go
Concat
import (
"fmt"
"strings"
)
func main() {
list := []string{"a", "b", "c"}
fmt.Println(strings.Join(list, ",")) // result "a,b,c"
}
In Js:
function main() {
const list = ['a', 'b', 'c'];
console.log(list.join(','));
}
For loops, switches, and conditionals
Go has no parens and no brace-less mode
func main() {
if a < b {
doStuff()
}
for i := 0; i < 10; ++i {
fmt.Print(i)
}
// can leave init/inc out:
i := 0
for i < 10 {
i += 1
}
// or "while(true) {}" with no terms:
for {
doForever();
}
// switch statements GET AN AUTOMATIC BREAK.
switch "a"+"b" {
case "ab":
fmt.Println("I run")
case "cd":
fmt.Println("I do not")
default:
fmt.Println("not a chance.")
}
}
- Switch statements can also have a prelude statement like ifs described below.
- Also, switch cases don't have to be constants, and the switch-eval short circuits before evaluating unnecessary case-expressions.
- Given that, they also added
switch {
to meanswitch true {
so you can use this as a funny "if/ifelse*/else" equivalent:
t := time.Now()
hr := t.Hour()
switch {
case hr <12:
t = "morning"
case hr <17:
t = "afternoon"
default:
t = "evening"
}
- You can turn on
fallthrough
https://go.dev/wiki/Switch#fall-through
Js
function main() {
if (a < b) doStuff();
for (let i = 0; i < 10; ++i) console.log(i);
for (let i = 0; i < 10; ++i) {
console.log(i);
}
}
Js-switches also short circuit:
const a = 42;
switch (a) {
case guess() - 1:
console.log("yay -1");
break;
case guess():
console.log("yay +0");
break;
case guess() + 1:
console.log("yay +1");
break;
}
function guess() {
console.log("guessing");
return 42;
}
results in two calls to guess()
Go conditional-scoped vars
You can semi-colon prepend a statement into the conditional test clause; any vars created have the scope of the conditional body:
func main() {
if a := 42; a < 50 {
fmt.Println(a, " is less than 50")
} else {
// still accessible here
fmt.Println(a, " is more than or equal to 50")
}
// "a" is _not_ accessible here
}
The equivalent in modern Js resembles C++ scoping:
function main() {
{ // create an extra scope
const a = 42;
if (a < 50) console.log(`${a} is less than 50`);
}
// but both languages support this in conditionals, where it's been the norm for decades. See above for loops.
}
Deferring cleanup
Go defer adds exit cleanup
Other than bash's exit traps, I'm only used to C-style. But Go has function-scope cleanup support:
func closeFile(handle) {
fmt.Println("closing", handle.filename)
}
func main() {
file := openFile('foo')
defer closeFile(file)
// do work with file, knowing close will be called at main-exit. (even during panic)
}
A case can be made that Go should have made this block scope, like later adopters did.
Js requires more manual handling of custom cleanup
function main() {
let file;
try {
file = openFileSync('foo');
// do work.
} catch (e) {
closeFile(file);
throw e;
}
closeFile(file);
}
C++ still wins on this one
in C++, I'd just use a smart pointer and a destructor.
Pointers: Go is "C w/o allowing pointer math"
func ptrmuckery() {
i := 42
pointer_to_i = &i
*pointer_to_i = 24
}
func setThisPtr(val int, dest *int) {
*dest = int
}
func main() {
var i int
setThisPtr(42, &i)
}
Js has pointers hiding in plain sight as every var is a reference. But you can't pass a pointer to a pointer syntactically, so the closest you could get to an output-formal-parameter-ref is to create an object:
function setThisPtr(val, dumb_handle) {
dumb_handle.ptr = val;
}
function main() {
const handle = {}; // I don't even _have_ to declare ptr if I don't want to.
setThisPtr(42, handle);
console.log(handle.ptr);
}
Structs
type Coord3 struct {
x,y,z float32
}
func main() {
fmt.Println(Coord3{1,2,3}) // result: `{1 2 3}`
// named terms also work, and unset fields follow the zeroish initialization rule:
other := Coord3{y: 42}
fmt.Println(other) // result: `{0 42 0}`
}
Js: everything is (or inherits from) POJO:
function main() {
const home = {x: 1, y: 2, z: 3};
console.log(home); // results: `{ x: 1, y: 2, z: 3 }`
}
// though there's a nice syntactic sugar that makes it look more OOP:
class Coord3 {
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
}
function main() {
const home = new Coord3(1,2,3);
console.log(home); // results: `Coord3 { x: 1, y: 2, z: 3 }`
}
Struct fields
In all languages I'm considering here, member access is simply "obj.member."
Go has an oddity for convenience: dereferecing a pointer to get at a member is also just a dot, unlike C's ->
.
func main() {
c := Coord3{1,2,3}
p := &c
c.x, (*p).x and p.x are the same thing.
}
Contrast: in C++ that would be p->x
and since Js only has references, dot is technically always an indirection.
OOP-ish
- https://go.dev/tour/methods/17
- https://go.dev/tour/methods/21
- how to decide on pointer-receiver vs value
package main
import (
"errors"
"fmt"
)
type GenericAccount interface {
Deposit(amount float64)
Withdraw(amount float64) error
}
type BankAccount struct {
balance float64
}
func (this *BankAccount) Deposit(amount float64) {
if this == nil {
fmt.Printf("BankAccount::Deposit(%v) called on a nil ptr\n", amount)
return
}
this.balance += amount
}
func (this *BankAccount) Accrue(percent float64) {
if this == nil {
fmt.Printf("BankAccount::Accrue(%v) called on a nil ptr\n", percent)
return
}
this.balance *= 1+percent
}
func (this *BankAccount) Withdraw(amount float64) error {
if this == nil {
return fmt.Errorf("BankAccount::Withdraw(%v) called on a nil ptr\n", amount)
}
if amount <= 0 {
// errors.New() is best suited for creating static, simple error messages that do not require dynamic content or wrapping other errors.
// For more complex error messages or error wrapping, fmt.Errorf() is often preferred.
return errors.New("can't be negative")
}
if this.balance < amount {
return errors.New("not enough to make withdrawal")
}
this.balance -= amount
return nil // if there are error returns, you can't leave the last one implicit.
}
func main() {
var real_account = BankAccount{balance: 100}
// since the methods are mutators, they're pointer-receivers.
// Because of that, it's only "*BankAccount" that implements GenericAccount, not plain value "BankAccount".
var acct GenericAccount = &real_account
// instance of interface ref will call the right concrete method
if e := acct.Withdraw(50); e != nil {
fmt.Println("error:", e)
}
fmt.Println("should be 50:", real_account.balance)
// but you have to dynamic cast to get at a leaf-only bit like Accrue(), and you can't cast to a value or it'll fail the interface's spec.
if ptr, ok := acct.(*BankAccount); ok {
ptr.Accrue(.1)
fmt.Println("should be ~55:", real_account.balance)
} else {
fmt.Println("we'd get here if it wasn't this concrete type")
}
}
Stringer String() and controlling fmt's pretty print
Anything implementing String() string
will be noticed by fmt:
type Acct struct {
Email string
Pass string
Name string
Age int
}
func (this Acct) String() string {
// return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
this.Pass = "[redacted]" // since this method is a value-receiver, we're not mutating the main copy
str, _ := json.Marshal(this)
return str
}
Errors
errors.New(str) can be used for trivial strings, but if you're formatting or wrapping, may as well use the newer Errorf.
- The wrapping system is the best for context chaining: https://go.dev/blog/go1.13-errors
- Ref for same? https://pkg.go.dev/errors (author blogged it https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully) https://pkg.go.dev/github.com/pkg/errors#section-readme
- See also https://pkg.go.dev/runtime#Stack
- official? https://pkg.go.dev/github.com/go-errors/errors#section-readme
- future reading https://pkg.go.dev/github.com/spacemonkeygo/errors#section-readme
The %w
specifier sets up error wrapping:
if sproing, err := func(); err != nil {
return nil, fmt.Errorf("could not func: %w", err)
}
}
You can test whether an error class is anywhere in the wrap-chain:
if errors.Is(err, package.ErrorClass) {
}
If you need the structure of an error class, the unwrapping cast-equivalent is:
var pErrorClass *package.ErrorClass // As() needs a pointer as an output ref
if errors.As(err, &pErrorClass) {
// use pErrorClass
}
Idiomatic
var (
ErrFubar = errors.New("first lib error")
)
func LibDo() {
return nil, ErrMyPackageErrar
// or
return nil, fmt.Errorf("stuff happened: %w", ErrMyPackageErrar)
}
and in the caller
func caller() {
derp, err := lib.LibDo()
if errors.Is(err, lib.ErrFubar) {
// handle case
}
}
An example of a big switch of errors.As()
Custom
When your func declares a return type of "error", that's a built-in interface:
type error interface {
Error() string
}
So you can define a custom error classish struct by implementing it:
import (
"fmt"
"os"
)
type PebkacError struct {
Who string
Keystrokes string
}
func (this PebkacError) Error() string {
return fmt.Sprintf("it's %v's fault because they pressed %v", this.Who, this.Keystrokes)
}
func fallableFunc() error {
return PebkacError{"Bob", "roflbbq"}
}
func main() {
if err := fallableFunc(); err != nil {
fmt.Println("FAIL:", err)
os.Exit(1)
}
}
And if your custom class implements either Unwrap() error
or Unwrap() []error
it must return nil (meaning "I'm the innermost error") or the inner error(s). (The array form is because errors.Join() does it that way; you can also have multiple "%w" in Errorf().)
Returnval Interception
Naming returns in the function sig isn't just a documentation thing: given a func with named returnvals, you can replace them on the way out with defer:
package main
import (
"fmt"
"errors"
)
func indecisive(question string) (resp string, err error) {
defer func() {
resp = "no! wait! yellow!"
err = errors.New("can't decide")
}()
switch (question) {
case "what... is your favorite color?":
resp = "blue!"
default:
resp = "I don't know"
}
return // mandatory, but implictly uses locals declared in the retval sig.
}
func main() {
if ans, err := indecisive("what... is your favorite color?"); err == nil {
fmt.Println("ans:", ans);
} else {
fmt.Println("err:", err, "answer given was:", ans);
}
}
Time
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println(time.Date(1933, 11, 22, 12, 34, 56, 78, time.UTC)) // 1933-11-22 12:34:56.000000078 +0000 UTC
start := time.Now()
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
elapsed := func() time.Duration {
return time.Since(start).Round(time.Millisecond)
}
for {
select {
case <-tick:
fmt.Printf("[%6s] tick.\n", elapsed())
case <-boom:
fmt.Printf("[%6s] BOOM!\n", elapsed())
return
default:
fmt.Printf("[%6s] .\n", elapsed())
time.Sleep(50 * time.Millisecond)
}
}
}
generics
- https://go.dev/doc/tutorial/generics
- https://go.dev/tour/generics/1
- blog that's using experimental: https://go.dev/blog/intro-generics
func MyFunc[Name rule, Name2 rule](var Name) Name {
return var
}
Rules:
comparable
is a built-in constraint tag that means "== and != are allowed"- the rule can be a type like
int64
or a union of them:int64 | uint64
- you can make your own type constraint:
type Number interface { int64 | float64 } func MyFunc[T Number](var T) {}
- You can add methods to a generic, but not a generic method.
- You can "overload" to take "string | *string" but it's ugly:
package main
import "fmt"
type Addable interface {
int | int8 | int16 | int32 | int64 | float32 |
uint | uint8 | uint16 | uint32 | uint64 | float64 |
string
}
func Sum[T Addable](nums ...T) T {
var sum T
for _, v := range nums {
sum += v
}
return sum
}
type List[T comparable] struct {
list []T
}
func (this *List[T]) indexOf(ref interface{}) int {
switch specific := ref.(type) {
case T:
return this.indexOf(&specific)
case *T:
for i, v := range this.list {
if v == *specific {
return i
}
}
default:
fmt.Println("didn't get a good type", ref)
}
return -1
}
func main() {
total := Sum(1,2,3)
fmt.Println("total:", total)
cats := List[string]{[]string{"Chaos","Soot","Spaz"}}
// testing *string, which was my original simple implementation:
ref := "Soot"
fmt.Println("should be 1:", cats.indexOf(&ref))
ref = "Monkey"
fmt.Println("should be -1:", cats.indexOf(&ref))
// testing "also takes string by value"
fmt.Println("should be 2:", cats.indexOf("Spaz"))
}
Arrays and Slices
import (
"encoding/json"
"fmt"
)
func main() {
// Go arrays have a unique declaration order, but after that are normal
var ten_ints [10]int // arrays are immutable and the length is technically part of the type.
ten_ints[0] = 5
// array literals have curlies, just like structs
luggage_password := [5]int{1,2,3,4,5}
// a constructor w/o length is mostly the same, but you're technically constructing a _slice_ not an array
luggage_password := []int{1,2,3,4,5}
// to auto-length a real array, use ellipsis (https://rtbell.dev/blog/golang/three-dots)
luggage_password := [...]int{1,2,3,4,5}
// slicing into a list-like with a pythonesque syntax:
subset := luggage_password[1:2]
// this results in a REF TO ORIGINALS; go slices are documented as not copying anything.
// which is end-exclusive zero based:
json,_ := json.Marshal(subset)
fmt.Printf("json string was %s\n", json) // result: [2], because it starts at offset 1 and ENDS-BEFORE offset 2.
// unspecified terms in a slicer default to "zero" and "array length"
// luggage_password[2:] VS luggage_password[:2] VS luggage_password[:]
}
Js Arrays are more like Go slices, as those are the two mutable ones.
function main() {
const luggage_password = [1,2,3,4,5];
const subset = luggage_password.slice(1,2); // N.B! SLICE is like go slicing; array.splice is a mutator with an unrelated signature!
console.log(subset); // [2]
}
Slices can re-acquire underlying elements
Since a go slice is just a window-reference to the underlying array, go exposes a way to move the endpoints by copy-constructor:
package main
import (
"fmt"
)
func main() {
luggage_password := [5]int{1,2,3,4,5}
temp := luggage_password[1:3]
fmt.Println("full array:", luggage_password, "sliced to [1:3]:", temp)
fmt.Println("len() respects the current end marker:", temp, "has len()", len(temp))
fmt.Println("cap() reports on currently unused elements:", temp, "has cap()", cap(temp))
temp = temp[:cap(temp)] // [:] respects the current length of the slice, so to grab all out-of-window elements...
fmt.Println("after extension, len() is", len(temp), "because" temp)
// you can't back a slice up past its current zero-point; trying [-1:] just errors with "invalid argument: index -1 (constant of type int) must not be negative"
// while an empty slice on an array is not nil, a slice without a foundation is nil testable:
var nil_slice []int
fmt.Println(nil_slice, len(nil_slice), cap(nil_slice), nil_slice == nil) // results: [] 0 0 true
empty_slice := luggage_password[len(luggage_password):len(luggage_password)]
fmt.Println(empty_slice, len(empty_slice), cap(empty_slice), empty_slice == nil) // results: [] 0 0 false
// Iterating a slice using for-range. (Also works on maps.)
for index, val := range luggage_password {
fmt.Println("offset", index, "is", val)
}
// don't need index?
for _, val := range luggage_password {
fmt.Println(val)
}
// don't need value in the loop control itself?
// unlike a func like Marshal()'s returnval of {bytes[], error?}, a for-range statement can work without terminal dummy "_"
for index := range luggage_password {
fmt.Println("testing offset", index)
}
}
Js iterations:
function main() {
// notice "for-in" vs "for-of"!
// POJOs as MAPS
const a_map = {a: 'val', another: 'value'};
for (const key in a_map) {
const val = a_map[key];
console.log({val, key});
}
// before the days of for-in, we had Object.entries() and its friends. Still valid but I wouldn't dupe a large dataset in perf-sensitive code.
for (const [key, val] of Object.entries(a_map)) {
console.log({val, key});
}
// ARRAYS
const a_list = [1,2,3,4,5];
for (const val of a_list) {
console.log("for-of a list", val);
}
// or if you want the index as well, use the primordial forms:
for (let i = 0; i < a_list.length; ++i) {
const val = a_list[i];
console.log({i, val});
}
a_list.forEach((val, i) => console.log({i, val}));
}
Using Slices as Dynamic Arrays
func main() {
// a convenience for making what another lang would call a dynamically sized array, initialized to zeroish:
// make(${SLICE TYPE}, ${len}, ${optional_capacity_parameter}) // cap defaults to len
nums := make([]int, 5, 10)
// WARN: append reserves the right to allocate a new array if space runs out, so it MAY mutate original's backing array.
// append(${orig_array}, ${more_elements...})
more_nums := append(nums, 12) // since spare cap was allocated in make(), the backing array for more_nums is the same as nums.
}
go:append() and Go Slices: usage and internals
Nesting: a literal list of structs
func main() {
// an array of structs initialized in the decl looks like:
// []struct {def} {initializers}
// note that def is comma-free but initializers is comma delimited and comma terminated if multiline. Yay consistency!
pairs := [] struct {
name string
age int
} {
{"bob", 42},
{"sue", 24},
{age: 100},
}
fmt.Println(pairs)
}
Maps
https://go.dev/tour/moretypes/20https://gobyexample.com/mapshttps://stackoverflow.com/questions/47579004/what-can-be-used-in-a-map-literal-instead-of-a-type-name-in-go
// map[KeyType]ValueType{Key1: Value1, Key2: Value2, ...}
menu := map[string]float64{
"eggs": 1.75,
"bacon": 3.22,
"sausage": 1.89,
}
type Coord struct {
x, y float32
}
var global_places = map[string]Coord {
"house": Coord{1,2},
// if the map-keytype is fixed, you can shorten the literal:
"other": {3,4},
}
func main() {
// "make()" can create empty maps
places := make(map[string]Coord)
places["home"] = Coord{1,2}
fmt.Println(places)
// delete() to delete an entry
delete(places, "home")
// an exist test is hidden on the getter:
_, home_exists_bool := places["home"]
}
Variadic functions
import "fmt"
var verbose = true
func dbgPrint(stuff ...any) {
if (verbose) {
fmt.Println(stuff...)
}
}
func main() {
dbgPrint("hello world")
}
Passing Functions and Closures
You could inline all your functypes, but a typedef is much more readable
func compute_inlined(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
type FloatyOp func(float64, float64) float64
func compute_typed(fn FloatyOp) float64 {
return fn(3, 4)
}
func main() {
salt := 3
summer := func(a, b float64) float64 {
return a+b + salt;
}
if compute_typed(summer) == 10 {
fmt.Println("pass")
}
}
Typecasting
package main
import "fmt"
type Person struct {
name string
age int
}
type Robot struct {
serial int
mission string
}
func main() {
var p interface{}
// Static-ish casts:
var x int = 42
x2 := uint8(x)
fmt.Println(x, "as byte:", x2)
// dynamic casts work on interface{}s, not primitives:
// so you can't say
// x3 := x.(string)
// fmt.Println(x, "as string:", x3)
// panics if error is not explicitly caught:
if false {
p = x
x3 := p.(string) // THROWS FATAL `panic: interface conversion: interface {} is int, not string`
fmt.Println(p, "as string:", x3)
}
// catching the ok/error output prevents panics:
x4, was_stringable := p.(string)
fmt.Println(x, "is string?", was_stringable)
fmt.Printf("typeof x4=%T but len()=%d\n", x4, len(x4))
p = Person{name: "Alice", age: 25}
// would not work because "interface{}" has no fields
// fmt.Println(p.name)
// fmt.Println(p.age)
// if I activate this, all raw Person casts would fail with "panic: interface conversion: interface {} is main.Robot, not main.Person"
if false {
p = Robot{42, "murder"}
} else {
// so cast p inline:
fmt.Println(p.(Person).name)
p2 := p.(Person) // or hold the ref
fmt.Println(p2.age)
}
// conversely, I can trigger the default block: and it'll say "type unknown: int"
if false {
p = 42
}
// using the prelude statement trick that statements like "if" support provides a succinct (if ugly) way to fork on dynamic casts:
if person, valid := p.(Person); valid {
fmt.Printf("Person path: is a %T\n", person) // %T says "main.Person"
fmt.Println(person.name)
fmt.Println(person.age)
} else if robot, valid := p.(Robot); valid {
fmt.Printf("Robot path: is a %T\n", robot) // %T says "main.Robot"
fmt.Println("object:", robot) // {42 murder}
} else {
fmt.Printf("type unknown: %T\n", p)
}
// though using the keyword 'type' is more elegant than the above:
switch actual := p.(type) {
case Robot:
fmt.Println("switch says robot", actual)
case Person:
fmt.Println("switch says person")
}
}
JSON
https://go.dev/blog/jsonhttps://gobyexample.com/jsonhttps://pkg.go.dev/encoding/json#Marshalhttps://pkg.go.dev/encoding/json#Unmarshalhttps://pkg.go.dev/encoding/json#RawMessage
sanity checking hooks will be called by dummy-dynamic-casting to Marshaller https://go.dev/doc/effective_go#blank_implements
Dear god. I should check out http://gregtrowbridge.com/golang-json-serialization-with-interfaces/ which was followed up by https://www.brimdata.io/blog/unmarshal-interface/ also sounds like vets talking: https://www.reddit.com/r/golang/comments/b7xgsp/consuming_unknown_json_fields/https://stackoverflow.com/questions/63913044/json-stringify-equivalent-in-golang-for-mapstringinterface reflect -> https://stackoverflow.com/questions/20170275/how-to-find-the-type-of-an-object-in-go extract reflection from this mess: https://www.accuweaver.com/2024/02/10/delighted-to-resolve-unexpected-consequences-in-go-json-marshal/
import "reflect"
func main() {
str := reflect.TypeOf([]int{1,2,3})
}
https://boldlygo.tech/posts/2019-12-19-go-json-tricks-array-as-struct/ (json and http) https://blog.boot.dev/golang/json-golang/
a nasty glitch from naive customization of marshalling hooks https://www.crowdstrike.com/en-us/blog/unexpected-adventures-in-json-marshaling/https://medium.com/@chaewonkong/go-and-json-a-comprehensive-guide-to-working-with-json-in-golang-143fa2dfa897
package main
import (
"encoding/json"
"fmt"
)
// ALTERNATIVELY, skip the tags and just use go-fields that are case-insensitive-matches for the JSON.
type CoverageExample struct {
Str string `json:"stringValue"`
Num float32 `json:"numberValue"`
Int int `json:"integerValue"`
Bt bool `json:"booleanTrueValue"`
Bf bool `json:"booleanFalseValue"`
Null any `json:"nullValue"`
Arr []any `json:"arrayValue"`
Obj struct {
Str string `json:"nestedString"`
Num float32 `json:"nestedNumber"`
} `json:"objectValue"`
}
var sample []byte = []byte(`
{
"stringValue": "This is a string",
"numberValue": 123.45,
"integerValue": 42,
"booleanTrueValue": true,
"booleanFalseValue": false,
"nullValue": null,
"objectValue": {
"nestedString": "Another string",
"nestedNumber": 99
},
"arrayValue": [
"first element",
2,
true,
null,
{ "arrayObject": "inside array" },
[ "nested", "array" ]
]
}
`)
func main() {
var data CoverageExample
err := json.Unmarshal(sample, &data)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", data)
bytes, err := json.Marshal(data)
if err != nil {
panic(err)
}
fmt.Printf("simply stringed: %#s\n", bytes)
bytes, err = json.MarshalIndent(data, "", "\t")
if err != nil {
panic(err)
}
fmt.Printf("prettyprinted:%#s\n", bytes)
bytes, err = json.MarshalIndent(data, ">", "\t")
if err != nil {
panic(err)
}
// note that I manually added the first line's prefix here:
fmt.Printf("example of prefix field: \n>%#s\n", bytes)
}
https://stackoverflow.com/questions/36224779/golang-json-struct-to-lowercase-doesnt-work?rq=3https://stackoverflow.com/questions/28644600/how-to-json-decode-lowercased-names-into-my-struct?rq=3https://stackoverflow.com/questions/24837432/capitals-in-struct-fields
Decoding Unknown JSON
If you don't have a struct compiled and need to inspect, you'll need to jump through any-ish hoops:
package main
import (
"encoding/json"
"fmt"
"reflect"
)
func DecodeStrangeJSON(bytes []byte) (out map[string]any, err error) {
err = json.Unmarshal(bytes, &out)
return
}
func DecodeJSON[Serializable any](bytes []byte) (out Serializable, err error) {
err = json.Unmarshal(bytes, &out)
return
}
type CoverageExample struct {
Str string `json:"stringValue"`
Num float32 `json:"numberValue"`
Int int `json:"integerValue"`
Bt bool `json:"booleanTrueValue"`
Bf bool `json:"booleanFalseValue"`
Null any `json:"nullValue"`
Obj struct {
Str string `json:"nestedString"`
Num float32 `json:"nestedNumber"`
} `json:"objectValue"`
Arr []any `json:"arrayValue"`
}
func main() {
sample := []byte(`
{
"stringValue": "This is a string",
"numberValue": 123.45,
"integerValue": 42,
"booleanTrueValue": true,
"booleanFalseValue": false,
"nullValue": null,
"objectValue": {
"nestedString": "Another string",
"nestedNumber": 99
},
"arrayValue": [
"first element",
2,
true,
null,
{ "arrayObject": "inside array" },
[ "nested", "array" ]
]
}
`)
fmt.Printf("sample: %s\n", sample)
raw, err := DecodeStrangeJSON(sample)
if err != nil {
panic(err)
}
for k, v := range raw {
fmt.Printf("top level [%s]<%T> = %v\n", k, v, v);
}
structured, err := DecodeJSON[CoverageExample](sample)
if err != nil {
panic(err)
}
fmt.Printf("structured? %#v\n", structured)
/*for k, v := range structured {
fmt.Printf("top level [%s]<%T> = %v\n", k, v, v);
}
*/
stru := reflect.ValueOf(structured) // we're working with the concrete side of the ptr, hence "value"
if stru.Kind() != reflect.Struct {
panic("this is a constant in the sample, just illustrating how to chekc")
}
ShowKeysInStruct("structured", structured)
return
for i := 0; i < stru.NumField(); i++ {
field := stru.Type().Field(i)
fmt.Println(field) // Print the field name
ShowKeysInStruct("field", field)
}
}
func ShowKeysInStruct(name string, thing any) {
meta := reflect.ValueOf(thing)
k := meta.Kind()
switch k {
case reflect.Struct:
for i := 0; i < meta.NumField(); i++ {
field := meta.Type().Field(i)
other := meta.Field(i)
fmt.Printf("%s.%s<%v> = %#v\n", name, field.Name, other.Kind(), other)
}
default:
fmt.Printf("%s is a %v (%T)\n", name, k, thing)
}
}
XML
HTTP RESTful
- Mentions of "mux" could be the internal "http.ServeMux" or "gorilla/mux"
- https://pkg.go.dev/context is for request context
usage of the built-in: https://www.alexedwards.net/blog/an-introduction-to-handlers-and-servemuxes-in-gohttps://dev.to/leapcell/gos-httpservemux-is-all-you-need-1mamhttps://leapcell.medium.com/gos-http-servemux-is-all-you-need-f33ad63ed2b1https://shijuvar.medium.com/building-rest-apis-with-go-1-22-http-servemux-2115f242f02bhttps://eli.thegreenplace.net/2023/better-http-server-routing-in-go-122https://www.kelche.co/blog/go/http-server/#:~:text=This server includes essential features,logging for debugging and monitoring
Making Calls into C
package main
// using a magic docstring: it can contain literal declarations, or linker flags for the cgo tool like
// #cgo LDFLAGS: -lm
/*
#include <stdio.h>
#include "your_custom_code.h"
*/
import "C"
func main() {
C.puts(C.CString("Hello from C called by Go!"))
}
It is also possible to expose Go functions as a C shared library or static library that can be called from C code. This involves building your Go package with the c-shared or c-archive build modes, which generate a C-compatible library and header file.
https://www.reddit.com/r/golang/comments/ayhql4/using_c_libraries_in_go/https://www.thegoldfish.org/2019/04/using-c-libraries-from-go/https://dev.to/metal3d/understand-how-to-use-c-libraries-in-go-with-cgo-3dbnhttps://github.com/lxwagn/using-go-with-c-libraries
Concurrency
- (Concurrency is not parallelism)
- https://go.dev/tour/concurrency/1
- https://go.dev/doc/faq#Concurrency
- he likes go concurrency: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/
- a book for later: https://greenteapress.com/wp/semaphores/
dirty lifehack: an empty select {}
will block forever but at least not burn cycles.
sync
atomic
chan
https://gobyexample.com/select
Receiving from a nil channel blocks forever. Receiving from a closed channel always succeeds, immediately returning the element type's zero value.
close(chan)
If you're not checking the ok, you'll get zeroish forever.
WaitGroups
Don't count goroutines manually, use the official class manually to manually increment and decrement your count.
https://gobyexample.com/waitgroups
Also, there's one that reaps the first error returned from any child goroutine: https://pkg.go.dev/golang.org/x/sync/errgroup
func naiveHelper() {
// do work
}
import "sync"
type Pool struct {
adder func()
sync.WaitGroup // embedded so you can say Pool.Wait
}
func (this *Pool) Add() {
this.Add(1) // inc the wg
go this.wrapper()
}
func (this *Pool) wrapper() {
}
func RunPool(launcherFunc func()) {
}
func main() {
wg := sync.WaitGroup // this example never passed it around, but be sure to pass by pointer to avoid mutating copies.
}
Letting the goroutine die even if never read
(This can come up if the reader has a timeout, so it may give up before reading you.)
Make the chan buffer 1; the final answer can be pushed without the goroutine pausing before teardown.
ref: https://gobyexample.com/timeouts
nonblocking is easy: https://gobyexample.com/non-blocking-channel-operations
giving up slice w/o reading a chan
runtime.Gosched
Channels
- https://go.dev/doc/effective_go#channels
- https://www.jtolio.com/2016/03/go-channels-are-bad-and-you-should-feel-bad/
Select and reflect.Select
Reflection
Embed Directive
- https://gobyexample.com/embed-directive
- https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives
- https://pkg.go.dev/embed
gRPC and Protobuf
https://protobuf.dev/best-practices/
in JS
- official: npm(@grpc/grpc-js) https://grpc.io/docs/languages/node/basics/
- official: npm(grpc-web) https://github.com/grpc/grpc-web
- POS does not support duplex streams OR WORK W/O A PROTOCOL PROXY
- wait, the hello world for grpc-web just uses @grpc/grpc-js anyway!
- recommended by postman
- npm install @gRPC/gRPC-js @gRPC/proto-loader https://blog.postman.com/how-to-build-a-grpc-api-in-node-js/
- same lib as postmans, but w/ cute TS decorator? https://docs.nestjs.com/microservices/grpc
- ALSO, notes on reflecting like it's formal-RESTful https://docs.nestjs.com/microservices/grpc#grpc-reflection
- and https://docs.nestjs.com supports duplex streaming https://docs.nestjs.com/microservices/grpc#grpc-streaming
- see also https://rxjs.dev/guide/overview
- AWS-heavy transcriber https://subaud.io/blog/node-grpc-server
- https://github.com/grpc-ecosystem/awesome-grpc?tab=readme-ov-file#lang-nodejs
- one candidate is https://github.com/connectrpc/connect-es
- using connect to demo protobuf unknown fields persistence https://kmcd.dev/posts/protobuf-unknown-fields/
Other notes
- Go doesn't want to work with shebang. Others have cobbled solutions but the sane answer is to wrap it instead of trying to get it to act like a bash script file.
- fancy (and boilerplate heavy) replacement for named args: https://uptrace.dev/blog/golang-functional-options
- network timeouts and naive context deadlines